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.