Get tab key to indent a block of code

The following solution seems to work reasonably well, at least on a few examples I have tested. It will be in the spirit of the one I gave to a rather similar earlier question you asked. I wasn't able to make the Tab key work, instead I bound the indenting to the CTRL+ ` combination - but in practice this is almost as easy as pressing Tab key. Also, the following is only a solution for indenting to the right. Indenting to the left is likely also possible, but might be a bit trickier to implement.

Here is the code. This is a generic function to construct a self-overwriting cell:

ClearAll[generateAutoOverwriteCell];
generateAutoOverwriteCell[
   boxes : Except[_?OptionQ] : "", 
   type_String: "Input", 
   opts___?OptionQ
] :=
Module[{},
   SelectionMove[EvaluationNotebook[], All, Cell, AutoScroll -> False];
   SelectionMove[EvaluationNotebook[], Previous, Cell, AutoScroll -> False]; 
   NotebookWrite[EvaluationNotebook[], Cell[BoxData[boxes], type, opts], All]; 
   SelectionMove[EvaluationNotebook[], All, CellContents, AutoScroll -> False]; 
   SelectionMove[EvaluationNotebook[], Previous, Character];
]

This is a relatively simple "formatter" for the selected part of your code:

ClearAll[process, $newlinePattern, $inner];

$newlinePattern = ("\n"|"\[IndentingNewLine]");

process[r_RowBox/;FreeQ[r,$newlinePattern]]:=RowBox[{"\t",r}];

process[x_]:=process[x,False];

process[RowBox[conts_List],flag_]:= RowBox[process[conts,flag]];

process[{left__,sep:$newlinePattern, right___}, flag_]:=
  {
     RowBox[{If[!TrueQ[$inner],"\t",Sequence@@{}],Sequence@@#}] & [
	     Block[{$inner = True},process[{left},False]]
     ]
     ,
     sep
     ,
     Sequence @@ Block[{$inner = False},process[{right}, True]]
  };

process[{args__}/;FreeQ[{args},$newlinePattern],True]:={RowBox[{"\t",args}]};

process[x_,_]:=x/.block:{__,$newlinePattern, ___}:>process[block,False];

This is the key action rule, which I bound to the CTRL+ ` combination:

ClearAll[selectionTabRule]
selectionTabRule = 
   {"KeyDown", "`"} :>
      With[{nb = InputNotebook[]}, 
        If[MemberQ[CurrentValue["ModifierKeys"], "Control"],
           With[{sel = NotebookRead[nb]},
              NotebookWrite[nb, process[sel]]
           ],
           (* else *)
           NotebookWrite[nb, "`"]
        ]
      ];

Finally, here is the short-cut to construct such cells:

ClearAll[incell];
incell := generateAutoOverwriteCell[CellEventActions -> {selectionTabRule}];

Now, if you type incell into a new cell and evaluate, you will get a new cell with desired behavior. If you then copy and paste some code into this cell, you can start playing with it. Select the piece of code you'd like to move to the right, and press CTRL+ `. The code you select should be a complete expression or a sequence of expressions.

I am sure there are bugs and limitations in this simple solution. It is more in the spirit of showing how things might work, than being a complete solution here. I just find it interesting to explore the possibilities the FrontEnd can give us.


Here is a much more modest try for blocks of code. :) You could expand it to cover text like @LeonidShifrin has shown. It could also be reversed to unindent.

SetOptions[$FrontEnd, 
 FrontEndEventActions -> {
  {"KeyDown", "\t"} :> 
   NotebookWrite[
    InputNotebook[], 
    Insert[
     NotebookRead[InputNotebook[]] /. 
      "\[IndentingNewLine]" -> 
       Sequence["\[IndentingNewLine]", "\t"],
     "\t", {1, 1}]]}]

Select the whole lines (at least make sure you have the beginning of each line, including the first) and press tab:

a
b
c

... a

... b

... c


I do much the same as Leonid, except I generally first convert the \[IndentingNewLine] structure into the appropriate tabified block:

indentingNewLineReplace[r : RowBox[data_]] :=
  RowBox@
   Replace[data, {
     "{" :>
      CompoundExpression[
       $indentationUnbalancedBrackets["{"]++,
       "{"
       ],
     "}" :>
      CompoundExpression[
       $indentationUnbalancedBrackets["{"] =
        Max@{$indentationUnbalancedBrackets["{"] - 1, 0},
       "}"
       ],
     "[" :>
      CompoundExpression[
       $indentationUnbalancedBrackets["["]++,
       "["
       ],
     "]" :>
      CompoundExpression[
       $indentationUnbalancedBrackets["["] =
        Max@{$indentationUnbalancedBrackets["["] - 1, 0},
       "]"
       ],
     "(" :>
      CompoundExpression[
       $indentationUnbalancedBrackets["("]++,
       "("
       ],
     ")" :>
      CompoundExpression[
       $indentationUnbalancedBrackets["("] =
        Max@{$indentationUnbalancedBrackets["("] - 1, 0},
       ")"
       ],
     r2_RowBox :>
      indentingNewLineReplace[r2],
     $indentingNewLine :>
      CompoundExpression[
       Map[
        Which[
          $indentationUnbalancedBrackets[#] > \
$intentationPreviousLevels[#],
          $indentationLevel[#]++,
          $indentationUnbalancedBrackets[#] < \
$intentationPreviousLevels[#],
          $indentationLevel[#] =
           Max@{$indentationLevel[#] - 1, 0}
          ] &,
        Keys@$indentationLevel],
       $intentationPreviousLevels = $indentationUnbalancedBrackets,
       "\n" <>
        If[Total@$indentationLevel > 0,
         StringRepeat["\t", Total@$indentationLevel],
         ""
         ]
       ]
     },
    1];

IndentingNewLineReplace[r : RowBox[data_]] :=
  Block[{
    $indentationUnbalancedBrackets =
     <|"[" -> 0, "{" -> 0, "(" -> 0|>,
    $intentationPreviousLevels =
     <|"[" -> 0, "{" -> 0, "(" -> 0|>,
    $indentationLevel =
     <|"[" -> 0, "{" -> 0, "(" -> 0|>
    },
   indentingNewLineReplace[r]
   ];
IndentingNewLineReplace[s_String] :=
  s;

IndentationReplace[nb_: Automatic] :=

  With[{inputNotebook = Replace[nb, Automatic :> InputNotebook[]]},
   With[{selection = IndentationSelection@inputNotebook},
    With[{write = IndentingNewLineReplace@selection},
     NotebookWrite[inputNotebook, write,
      If[MatchQ[write, _String?(StringMatchQ[Whitespace])],
       After,
       All]]
     ]
    ]
   ];

Then I do a standard simple recursive replacement:

IndentationSelection[inputNotebook_] :=

  Replace[NotebookRead@inputNotebook, {
    Cell[BoxData[d_] | d_String, ___] :>
     CompoundExpression[
      SelectionMove[First@SelectedCells[], All, CellContents],
      d]
    }];

indentationAddTabsRecursiveCall[RowBox[d : {___}]] :=
  RowBox@
   Replace[d, {
     r_RowBox :>
      indentationAddTabsRecursiveCall[r],
     s_String?(StringMatchQ[$indentingNewLine ~~ ___]) :>

      StringInsert[StringDrop[s, 1], "\n\t", 1],
     s_String?(StringMatchQ["\n" ~~ ___]) :>

      StringInsert[s, "\t", 2]
     },
    1];
indentationAddTabs[sel_] :=
  Replace[
   sel, {
    {} :> "\t",
    _String :>
     StringReplace[sel, {
       "\n" :> "\n\t",
       StartOfString :> "\t"
       }],
    _ :>
     Replace[indentationAddTabsRecursiveCall[sel],
      RowBox[{data___}] :>
       RowBox[{"\t", data}]
      ]
    }];

IndentationIncrease[nb_: Automatic] :=

  With[{inputNotebook = Replace[nb, Automatic :> InputNotebook[]]},
   With[{write = 
      indentationAddTabs@IndentationSelection@inputNotebook},
    NotebookWrite[inputNotebook, write,
     If[MatchQ[write, _String?(StringMatchQ[Whitespace])],
      After,
      All]]
    ]
   ];

And then you can put this def in a stylesheet or attach it to a cell / notebook:

IndentationEvent[] :=
  If[AllTrue[
    {"OptionKey", "ShiftKey"},
    CurrentValue[EvaluationNotebook[], #] &
    ],
   IndentationRestore[],
   Which[
    Not@FreeQ[NotebookRead@EvaluationNotebook[], $indentingNewLine],
    IndentationReplace[],
    CurrentValue["OptionKey"],
    IndentationDecrease[],
    True,
    IndentationIncrease[]
    ]
   ];

{
 {"KeyDown", "\t"} :>
  Quiet@Check[
    Needs["BTools`"];
    IndentationEvent[],
    SetAttributes[EvaluationCell[], CellEventActions -> None]
    ],
 PassEventsDown -> False
 }

It's a lot of code, but it is generally quite robust in my experience. The only standard issue I have is that the indenting-newline to tab replacer doesn't appropriately pick up special indenting characters like = and ->. It only catches the block indents.