Function Repository Resource:

ExpressionLineDiff

Source Notebook

Highlight the changes between two expressions

Contributed by: Richard Hennigan (Wolfram Research)

ResourceFunction["ExpressionLineDiff"][expr1,expr2]

highlights changes between expr1 and expr2.

Details and Options

ResourceFunction["ExpressionLineDiff"] displays changes between two expressions in a manner similar to many Git diff utilities that are used to display file changes.
ResourceFunction["ExpressionLineDiff"] has the following options:
"TrimmingThreshold"2the maximum number of consecutive unchanged lines to display
"IndentSize"1the number of spaces to use per indentation level
"PageWidth"100the desired formatting width of the output
PerformanceGoal"Quality"control the level of detail in the highlighting
"MergeThreshold"2minimum length of consecutive unchanged characters required to merge into surrounding changes
ResourceFunction["ExpressionLineDiff"] uses the resource function ReadableForm to convert expressions to lines.

Examples

Basic Examples (2) 

Highlight the differences between two expressions on a per-line basis:

In[1]:=
expr1 = Cell[BoxData[RowBox[{"1", "+", "1"}]], "Input", CellLabel -> "In[1]:=", CellTags -> {"neat"}, CellID -> 731245485];
expr2 = Cell[BoxData[RowBox[{"1", "+", "1"}]], "Input", CellLabel -> "In[2]:=", CellTags -> {"neat"}, CellID -> 731245435];
In[2]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2]
Out[2]=

Check larger expressions:

In[3]:=
expr1 = ResourceFunction["BirdSay", All, ResourceVersion -> "2.0.0"];
expr2 = ResourceFunction["BirdSay", All, ResourceVersion -> "3.0.0"];
In[4]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2]
Out[4]=

Options (9) 

TrimmingThreshold (2) 

Control the maximum number of consecutive unchanged lines to display:

In[5]:=
expr1 = ResourceFunction["BirdSay", All, ResourceVersion -> "2.0.0"];
expr2 = ResourceFunction["BirdSay", All, ResourceVersion -> "3.0.0"];
In[6]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, "TrimmingThreshold" -> 3]
Out[6]=

Show all lines:

In[7]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, "TrimmingThreshold" -> None]
Out[7]=

IndentSize (1) 

Adjust the amount of indentation:

In[8]:=
expr1 = ResourceFunction["BirdSay", All, ResourceVersion -> "2.0.0"];
expr2 = ResourceFunction["BirdSay", All, ResourceVersion -> "3.0.0"];
In[9]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, "IndentSize" -> 8, "TrimmingThreshold" -> None]
Out[9]=

PageWidth (2) 

Reducing the page width will increase the number of lines:

In[10]:=
expr1 = ResourceFunction["BirdSay", All, ResourceVersion -> "2.0.0"];
expr2 = ResourceFunction["BirdSay", All, ResourceVersion -> "3.0.0"];
In[11]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, "PageWidth" -> 30]
Out[11]=

Conversely, a larger page width will decrease the number of lines:

In[12]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, "PageWidth" -> 150]
Out[12]=

PerformanceGoal (2) 

By default, ExpressionLineDiff will use darker colors to highlight per-character changes:

In[13]:=
expr1 = ResourceFunction["BirdSay", All, ResourceVersion -> "2.0.0"];
expr2 = ResourceFunction["BirdSay", All, ResourceVersion -> "3.0.0"];
In[14]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2]
Out[14]=

With the option setting PerformanceGoal"Speed", only per-line highlighting will be used:

In[15]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, PerformanceGoal -> "Speed"]
Out[15]=

MergeThreshold (2) 

Control how many unchanged characters get merged into surrounding changes:

In[16]:=
expr1 = "1234567890";
expr2 = "1a3b56c890d";
In[17]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, "MergeThreshold" -> 2]
Out[17]=
In[18]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, "MergeThreshold" -> 1]
Out[18]=

A merge threshold of zero highlights exact changes:

In[19]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, "MergeThreshold" -> 0]
Out[19]=

Applications (3) 

Quickly find why two expressions that otherwise appear identical are different:

In[20]:=
expr1 = PieChart[{1, 2, 3}]
Out[20]=
In[21]:=
expr2 = PieChart[{1, 2, 3}]
Out[21]=

These are not in fact SameQ:

In[22]:=
expr1 === expr2
Out[22]=

The difference is due to underlying Dynamic elements that use different local variables:

In[23]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2]
Out[23]=

Possible Issues (2) 

ExpressionLineDiff compares InputForm strings, so highlighting does not necessarily align with subexpressions:

In[24]:=
expr1 = {123, 456, 789, xy, 0};
expr2 = {123, 4567, 789, xz, 0};
In[25]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2]
Out[25]=

This is comparable to using SequenceAlignment on the string representations:

In[26]:=
SequenceAlignment[ToString[expr1], ToString[expr2]]
Out[26]=
In[27]:=
% /. {a_String, b_String} :> Column[{Item[a, Background -> LightRed], Item[b, Background -> LightGreen]}]
Out[27]=

For flat lists, SequenceAlignment can identify changes per element:

In[28]:=
SequenceAlignment[expr1, expr2]
Out[28]=
In[29]:=
% /. {a_List, b_List} :> Column[{Item[a, Background -> LightRed], Item[b, Background -> LightGreen]}]
Out[29]=

With the option setting PerformanceGoal"Speed", ExpressionLineDiff only looks for changes on a per-line basis and does not highlight detailed changes:

In[30]:=
expr1 = Range[25];
expr2 = ReplacePart[expr1, 13 -> 31];
In[31]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, PerformanceGoal -> "Speed"]
Out[31]=

Using a smaller page width can increase the accuracy of highlighting at the cost of readability:

In[32]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, "PageWidth" -> 10, PerformanceGoal -> "Speed"]
Out[32]=

For small expressions, it's better to use the default option setting PerformanceGoal"Quality" to add additional per-character highlighting:

In[33]:=
ResourceFunction["ExpressionLineDiff"][expr1, expr2, PerformanceGoal -> "Quality"]
Out[33]=

Version History

  • 1.1.2 – 17 January 2023
  • 1.1.1 – 21 August 2020
  • 1.1.0 – 17 August 2020
  • 1.0.0 – 13 May 2020

Related Resources

License Information