diff --git a/CHANGELOG.txt b/CHANGELOG.txt
new file mode 100644
index 0000000..62e1940
--- /dev/null
+++ b/CHANGELOG.txt
@@ -0,0 +1,178 @@
+CHANGELOG for Diff 7.x-2.x
+
+1) System variable names have been changed
+------------------------------------------
+
+  Diff system variables were prefixed with "diff_" and node type specific
+  variables were tagged with the entity type 'node'
+
+  The full list of variable name changes include:
+    - enable_revisions_page_TYPE to  diff_enable_revisions_page_node_TYPE
+    - show_diff_inline_TYPE      to  diff_show_diff_inline_node_TYPE
+    - show_preview_changes_TYPE  to  diff_show_preview_changes_node_TYPE
+
+  The corresponding changes would be used like:
+
+<?php
+-    variable_get('enable_revisions_page_' . $node_type, 1);
++    variable_get('diff_enable_revisions_page_node_' . $node_type, 1);
+?>
+
+2) hook_diff() was removed
+--------------------------
+
+This has been replaced by hook_entity_diff() as of Diff 7.x-3.x.
+
+3) Field diffs are handled independently by Diff and the field module
+---------------------------------------------------------------------
+
+Field modules SHOULD NOT implement hook_entity_diff().
+
+This is complicated and costly in terms of performance.
+
+Two new field callbacks are defined to handle these.
+
+  a) MODULE_field_diff_view_prepare()
+
+  Optional: If you need to load data, use MODULE_field_diff_view_prepare().
+
+  b) MODULE_field_diff_view()
+
+  Recommended: You should implement this to generate the compared data.
+
+If there is no corresponding hook for a field, the field comparison will try
+to guess the value using $item['safe_value'] or $item['value'] properties.
+
+If you need to make this configurable, there are two additional hooks:
+
+  c) MODULE_field_diff_default_options($field_type)
+  
+  You should define any additioal settings here. This shares a global namespace
+  of the diff module, so you can overwrite core Diff settings here too.
+  
+  In saying that, take care not to accidentially do this.
+  
+  d) MODULE_field_diff_options_form($field_type, $settings)
+  
+  This is where you insert Form API elements to configure your option settings.
+
+4) Field diffs are now configurable
+-----------------------------------
+
+Each field type defined by core (with exception of number module) have
+configurable settings to control the rendering of the comparison.
+
+  a) Global configuration
+
+  An administration page has been added to handle field type default settings.
+  
+  This is the preferred way to configure field settings are these are global to
+  all fields of this type.
+
+  b) View mode display options
+
+  Requires the Field formatter settings (field_formatter_settings) module to
+  do anything other than toggle the field visibility and weight.
+
+  This allows for fine grain control per content type and per bundle.
+
+  For the Standard comparison, used when comparing different revisions, you
+  need to do the following: Basic page (page) is used in these examples.
+
+  - Check "Use Diff standard view mode when doing standard field comparisons"
+
+    Navigate to admin/structure/types/manage/page and look at the Diff settings.
+
+  - Enable "Diff standard" custom view mode
+
+    Navigate to admin/structure/types/manage/page/display and look at the
+    Custom Display Settings for this view mode. Check and save.
+
+  - Configure the display
+
+    After Saving this page, a new tab appears "Diff standard", click this or
+    navigate directly to admin/structure/types/manage/page/display/diff_standard
+
+    - Options without Field formatter settings module
+      These are limited. You can hide or show the fields that you want to
+      display when doing comparisons.
+
+    - Options with Field formatter settings module
+      All of the global variables mentioned above can be edited per field. Click
+      the small clog wheel beside the fields saying "Diff custom settings: No".
+
+      Check "Enable custom Diff settings" and configure the options that you
+      want to enable / configure. Click Update, and **Important** Save the page
+      using the buttons at the bottom.
+
+5) Standard comparison preview / Inline diff view setting
+---------------------------------------------------------
+
+You can set the view modes used to compare the rendered node. This can be found
+in the Diff settings in the Content Type settings page.
+
+6) Optional CSS and new Boxes styles
+------------------------------------
+
+This takes the styles from WikiPedia to really spice up the diff page.
+
+7) Optional JScript extras
+--------------------------
+
+This spices up the revision checkboxes on the revisions page.
+
+8) Simple past revision token support
+-------------------------------------
+
+Use-case, email notifications when content has changes. If these support tokens,
+then you can embed Diffs into these emails.
+
+9) And much more...
+-------------------
+
+The complete change log follows:
+
+Diff 7.x-2.x
+    o #888680 by Deciphered, Alan D.: Allow modules to interact via drupal_alter()
+    o #1280892 by Alan D., crea: Diff should track the variables that it defines
+    o #1304658 by Alan D., kari.kaariainen: Remove links and comments from the comparison preview
+    o #1122206 by binford2k, Alan D.: Notices thrown by DiffEngine::process_chunk()
+    o #1175064 by zilverdistel, Alan D.: Provide variables for leading and trailing context
+    o #1673864 by Alan D.: Allow users to bypass the admin theme when viewing comparisons
+    o #1673876 by Alan D.: Use Drupal autoloading for classes
+    o #1673856 by Alan D.: Use hook_form_BASE_FORM_ID_alter() rather than hook_form_alter()
+    o #1673856 by Alan D.: Normalise line endings
+    o #114308 by Alan D.: add jQuery for hiding radios that shouldn't show diffs
+    o #372957 by erykmynn, JuliaKM, lsrzj, andrew_rs, alexpott, et al: HTML Strip for Diff, WYSIWYG Friendly
+      (This was refactored in the 7.x-3.x branch from the commited 7.x-2.x code)    
+    o #521212 by Alan D., blakehall: Make diff comparison page themable
+    o #1671484 by Alan D.: Show number of lines changed on revisions page
+    o #114699 by smokris, Alan D.: Diff module should support Token
+    o #372957 by c31ck: display either Hide or Show based on what clicking it will do at any time (HTML Strip for Diff)
+      This was altered for the 7.x-3.x branch.
+
+    Node to Entity changes
+    ----------------------
+    These are roughly tracked in the meta issue #1365750 Generalize API and Integrate with core field types
+
+    o (no issue) by Alan D.: Use entity specific system variables.
+    o (no issue) by Alan D.: View mode code, new hooks, new API. Massive patch!
+
+    Resolves:
+    o #248778: Taxonomy diff
+    o #1550698: Diff of "select from list" fields shows change in key, not change in value
+    o #1458814: File (and image) field support
+    o #1418760: Optional setting to honour the display settings
+    o #1347316: Selectable view mode for inline diffs and "Current revision" display view mode
+    o #1458906: Improve performances (of existing 7.x-2.x field rendering)
+    o #1424162: Diff in Taxonomy term description
+    o #1211282: Image diff support
+
+The following patches will be posted in the corresponding project queues once
+the 7.x-3.x branch is released:
+    o #1595702 by Alan D., mbilbille: Support of field collection module
+    o #1350604 by Alan D., johaziel: Datetime diff
+    o (no issue) by Alan D.: Email field Diff support
+    o (no issue) by Alan D.: Countries Diff support
+    o (no issue) by Alan D.: Name field Diff support
+    o (no issue) by Alan D.: Link field Diff support
diff --git a/DiffEngine.php b/DiffEngine.php
index 510189c..8af9e48 100644
--- a/DiffEngine.php
+++ b/DiffEngine.php
@@ -1,5 +1,4 @@
 <?php
-// $Id$
 
 /**
  * @file
@@ -799,7 +798,6 @@ class DiffFormatter {
    * @return string The formatted output.
    */
   function format($diff) {
-
     $xi = $yi = 1;
     $block = FALSE;
     $context = array();
@@ -851,6 +849,14 @@ class DiffFormatter {
       $this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block);
     }
     $end = $this->_end_diff();
+
+    if (!empty($xi)) {
+      $this->line_stats['counter']['x'] += $xi;
+    }
+    if (!empty($yi)) {
+      $this->line_stats['counter']['y'] += $yi;
+    }
+
     return $end;
   }
 
@@ -1084,10 +1090,14 @@ class WordLevelDiff extends MappedDiff {
 class DrupalDiffFormatter extends DiffFormatter {
 
   var $rows;
+  var $line_stats = array(
+    'counter' => array('x' => 0, 'y' => 0),
+    'offset' => array('x' => 0, 'y' => 0),
+  );
 
   function DrupalDiffFormatter() {
-    $this->leading_context_lines = 2;
-    $this->trailing_context_lines = 2;
+    $this->leading_context_lines = variable_get('diff_leading_context_lines', 2);
+    $this->trailing_context_lines = variable_get('diff_trailing_context_lines', 2);
   }
 
   function _start_diff() {
@@ -1101,11 +1111,11 @@ class DrupalDiffFormatter extends DiffFormatter {
   function _block_header($xbeg, $xlen, $ybeg, $ylen) {
     return array(
       array(
-        'data' => theme('diff_header_line', array('lineno' => $xbeg)),
+        'data' => theme('diff_header_line', array('lineno' => $xbeg + $this->line_stats['offset']['x'])),
         'colspan' => 2,
       ),
       array(
-        'data' => theme('diff_header_line', array('lineno' => $ybeg)),
+        'data' => theme('diff_header_line', array('lineno' => $ybeg + $this->line_stats['offset']['y'])),
         'colspan' => 2,
       )
     );
@@ -1128,10 +1138,13 @@ class DrupalDiffFormatter extends DiffFormatter {
    */
   function addedLine($line) {
     return array(
-      '+',
+      array(
+        'data' => '+',
+        'class' => 'diff-marker',
+      ),
       array(
         'data' => theme('diff_content_line', array('line' => $line)),
-        'class' => 'diff-addedline',
+        'class' => 'diff-context diff-addedline',
       )
     );
   }
@@ -1141,10 +1154,13 @@ class DrupalDiffFormatter extends DiffFormatter {
    */
   function deletedLine($line) {
     return array(
-      '-',
+      array(
+        'data' => '-',
+        'class' => 'diff-marker',
+      ),
       array(
         'data' => theme('diff_content_line', array('line' => $line)),
-        'class' => 'diff-deletedline',
+        'class' => 'diff-context diff-deletedline',
       )
     );
   }
@@ -1197,7 +1213,7 @@ class DrupalDiffFormatter extends DiffFormatter {
 
     while ($line = array_shift($del)) {
       $aline = array_shift( $add );
-      $this->rows[] = array_merge($this->deletedLine($line), $this->addedLine($aline));
+      $this->rows[] = array_merge($this->deletedLine($line), isset($aline) ? $this->addedLine($aline) : $this->emptyLine());
     }
     foreach ($add as $line) {  // If any leftovers
       $this->rows[] = array_merge($this->emptyLine(), $this->addedLine($line));
@@ -1272,6 +1288,9 @@ class DrupalDiffInline {
     $j = 0;
     foreach ($chunk as $i => $piece) {
       $next = isset($chunk[$i+1]) ? $chunk[$i+1] : NULL;
+      if (!isset($processed[$j])) {
+        $processed[$j] = '';
+      }
       if (strpos($piece, '<') === 0 && drupal_substr($piece, drupal_strlen($piece) - 1) === '>') {
         $processed[$j] = $piece;
         $j++;
diff --git a/css/diff.boxes.css b/css/diff.boxes.css
new file mode 100644
index 0000000..ee1b311
--- /dev/null
+++ b/css/diff.boxes.css
@@ -0,0 +1,124 @@
+
+html.js .diff-js-hidden { display: none; }
+
+/* Reset as many core themes as possible */
+table.diff {
+  font-size: 0.923em;
+  margin: 0 0 10px;
+  border: 0 none;
+  width: 98%;
+  border-spacing: 5px;
+  table-layout: fixed ;
+  border-collapse: separate;
+}
+table.diff tr td:last-child {
+  border-right: inherit;
+}
+table.diff td,
+table.diff th {
+  vertical-align: middle;
+  border: 0 none;
+  color: #000;
+  text-transform: none;
+  background: none;
+  border-spacing: 4px;
+  padding: 4px 8px;
+}
+table.diff tr, table.diff tr.even {
+  background: none;
+}
+table.diff tr th, table.diff tr th a, table.diff tr th a:hover {
+  color: inherit;
+  font-weight: bold;
+}
+table.diff tr.even,
+table.diff tr.odd {
+  border-width: 0;
+  border-style: none;
+  background: transparent;
+}
+table.diff th a { display: inline; }
+
+/* Main theming */
+table.diff, td.diff-number {
+  background-color: white
+}
+
+table.diff td.diff-lineno {
+  font-weight: bold
+}
+
+table.diff td.diff-addedline, table.diff td.diff-deletedline, table.diff td.diff-context {
+  font-size: 88%;
+  vertical-align: top;
+  white-space: -moz-pre-wrap;
+  white-space: pre-wrap
+}
+
+table.diff td.diff-addedline, table.diff td.diff-deletedline {
+  border-style: solid;
+  border-width: 1px 1px 1px 4px;
+  border-radius: 0.33em
+}
+
+table.diff td.diff-context {
+  background: #f3f3f3;
+  color: #333333;
+  border-style: solid;
+  border-width: 1px 1px 1px 4px;
+  border-color: #e6e6e6;
+  border-radius: 0.33em;
+}
+table.diff td.diff-addedline {
+  border-color: #a3d3ff;
+  background: #ffffff;
+  border: 1px 1px 1px 3px;
+}
+
+table.diff td.diff-deletedline {
+  border-color: #ffe49c
+}
+
+.diffchange {
+  font-weight: bold;
+  text-decoration: none
+}
+
+table.diff td.diff-addedline .diffchange, table.diff td.diff-deletedline .diffchange {
+  border-radius: 0.33em;
+  padding: 0.25em 0
+}
+
+table.diff td.diff-addedline .diffchange {
+  background: #d8ecff
+}
+
+table.diff td.diff-deletedline .diffchange {
+  background: #feeec8
+}
+
+table.diff table.diff td {
+  padding: 0.33em 0.66em
+}
+
+table.diff td.diff-marker {
+  width: 2%;
+  text-align: right;
+  font-weight: bold;
+  font-size: 1.25em  
+}
+
+table.diff col.diff-content {
+  width: 48%
+}
+
+table.diff table.diff td div {
+  word-wrap: break-word;
+  overflow: auto
+}
+td.diff-prevlink {
+  text-align: left;
+}
+td.diff-nextlink {
+  text-align: right;
+}
diff --git a/css/diff.default.css b/css/diff.default.css
new file mode 100644
index 0000000..8187561
--- /dev/null
+++ b/css/diff.default.css
@@ -0,0 +1,86 @@
+
+html.js .diff-js-hidden { display: none; }
+
+/**
+ * Inline diff metadata
+ */
+.diff-inline-metadata {
+  padding:4px;
+  border:1px solid #ddd;
+  background:#fff;
+  margin:0px 0px 10px;
+}
+
+.diff-inline-legend { font-size:11px; }
+
+.diff-inline-legend span,
+.diff-inline-legend label { margin-right:5px; }
+
+/**
+ * Inline diff markup
+ */
+span.diff-deleted { color:#ccc; }
+span.diff-deleted img { border: solid 2px #ccc; }
+span.diff-changed { background:#ffb; }
+span.diff-changed img { border:solid 2px #ffb; }
+span.diff-added { background:#cfc; }
+span.diff-added img { border: solid 2px #cfc; }
+
+/**
+ * Traditional split diff theming
+ */
+table.diff {
+  border-spacing: 4px;
+  margin-bottom: 20px;
+  table-layout: fixed;
+  width: 100%;
+}
+table.diff tr.even, table.diff tr.odd {
+  background-color: inherit;
+  border: none;
+}
+td.diff-prevlink {
+  text-align: left;
+}
+td.diff-nextlink {
+  text-align: right;
+}
+td.diff-section-title, div.diff-section-title {
+  background-color: #f0f0ff;
+  font-size: 0.83em;
+  font-weight: bold;
+  padding: 0.1em 1em;
+}
+td.diff-deletedline {
+  background-color: #ffa;
+  width: 50%;
+}
+td.diff-addedline {
+  background-color: #afa;
+  width: 50%;
+}
+td.diff-context {
+  background-color: #fafafa;
+}
+span.diffchange {
+  color: #f00;
+  font-weight: bold;
+}
+
+table.diff col.diff-marker {
+  width: 1.4em;
+}
+table.diff col.diff-content {
+  width: 50%;
+}
+table.diff th {
+  padding-right: inherit;
+}
+table.diff td div {
+  overflow: auto;
+  padding: 0.1ex 0.5em;
+  word-wrap: break-word;
+}
+table.diff td {
+  padding: 0.1ex 0.4em;
+}
diff --git a/diff.admin.inc b/diff.admin.inc
new file mode 100644
index 0000000..f5a015d
--- /dev/null
+++ b/diff.admin.inc
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @file
+ * Administration page callbacks and forms.
+ */
+
+/**
+ * General configuration form for controlling the diff behaviour.
+ */
+function diff_admin_settings($form, $form_state) {
+  $form['diff_theme'] = array(
+    '#type' => 'select',
+    '#title' => t('CSS'),
+    '#default_value' => variable_get('diff_theme', 'default'),
+    '#options' => array(
+      'default' => t('Classic'),
+      'boxes' => t('Boxes'),
+    ),
+    '#empty_option' => t('None'),
+  );
+  $form['diff_script_revisioning'] = array(
+    '#type' => 'select',
+    '#title' => t('Revisioning JScript Options'),
+    '#default_value' => variable_get('diff_script_revisioning', 'simple'),
+    '#options' => array(
+      'simple' => t('Simple exclusion'),
+      'linear' => t('Linear restrictions'),
+    ),
+    '#empty_option' => t('None'),
+    '#description' => t('<em>Simple exclusion</em> means that users will not be able to select the same revision, <em>Linear restrictions</em> means that users can only select older or newer revisions of the current selections.'),
+  );
+
+  $options = drupal_map_assoc(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
+  $form['diff_leading_context_lines'] = array(
+    '#type' => 'select',
+    '#title' => t('Leading context lines'),
+    '#description' => t('This governs the number of unchanged leading context "lines" to preserve.'),
+    '#default_value' => variable_get('diff_leading_context_lines', 2),
+    '#options' => $options,
+  );
+  $form['diff_trailing_context_lines'] = array(
+    '#type' => 'select',
+    '#title' => t('Trailing context lines'),
+    '#description' => t('This governs the number of unchanged trailing context "lines" to preserve.'),
+    '#default_value' => variable_get('diff_trailing_context_lines', 2),
+    '#options' => $options,
+  );
+  $form['diff_normalise_text'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Normalise text'),
+    '#description' => t('Convert all Windows and Mac newlines to a single newline, so diff only need to deal with one newline type. Unix users should have no need to enable this option.'),
+    '#default_value' => variable_get('diff_normalise_text', 0),
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Global entity settings.
+ */
+function diff_admin_global_entity_settings($form, $form_state, $entity_type) {
+  $form['diff_show_header_' . $entity_type] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Show entity label header'),
+		'#default_value' => variable_get('diff_show_header_' . $entity_type, 1),
+  );
+  $form['diff_admin_path_' . $entity_type] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Treat diff pages as administrative'),
+    '#description' => t('Diff pages are treated as administrative pages by default, although it is up to each module to enforce this and to implement this optional setting.'),
+	  '#default_value' => variable_get('diff_admin_path_' . $entity_type, 1),
+  );
+  return system_settings_form($form);
+}
+
+/**
+ * Menu callback - provides an overview of Diff support and global settings.
+ */
+function diff_admin_field_overview() {
+  $build['info'] = array(
+    '#markup' => t('<p>This table provides a summary of the field type support found on the system. It is recommended that you use global settings whenever possible to configure field comparison settings.</p>'),
+  );
+
+  $header = array(t('Type'), t('Module'), t('Operations'));
+  $rows = array();
+
+  // Skip field types which have no widget types.
+  $field_types = field_info_field_types();
+  $widgets = array();
+  foreach (field_info_widget_types() as $name => $widget_type) {
+    foreach ($widget_type['field types'] as $widget_field_type) {
+      if (isset($field_types[$widget_field_type])) {
+        $widgets[$widget_field_type][$name] = $widget_type['label'];
+      }
+    }
+  }
+
+  foreach ($field_types as $field_name => $field_type) {
+    if (!empty($widgets[$field_name])) {
+      $row = array();
+      $row[] = t('@field_label (%field_type)', array(
+        '@field_label' => $field_type['label'],
+        '%field_type' => $field_name,
+      ));
+      $row[] = $field_type['module'];
+      $row[] = l(t('Global settings'), 'admin/config/content/diff/fields/' . $field_name);
+      $rows[] = $row;
+    }
+  }
+
+  $build['category_table'] = array(
+    '#theme' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+    '#empty' => t('The system has no configurable fields.'),
+  );
+  return $build;
+}
+
+/**
+ * Menu form callback for the field settings.
+ */
+function diff_admin_global_field_settings($form, $form_state, $type) {
+  module_load_include('diff.inc', 'diff');
+
+  $field_types = field_info_field_types();
+  if (!isset($field_types[$type])) {
+    drupal_set_message(t('Invalid field type.'), 'error');
+    drupal_goto('admin/config/content/diff/fields');
+  }
+  $field_type = $field_types[$type];
+
+  // Set the title to give more context to this page.
+  drupal_set_title(t('Global settings for %label fields', array(
+    '%label' => $field_type['label'],
+  )), PASS_THROUGH);
+
+  $variable_name = "diff_{$field_type['module']}_field_{$type}_default_options";
+  $settings = variable_get($variable_name, array());
+  $settings = _diff_field_default_settings($field_type['module'], $type, $settings);
+  $func = $field_type['module'] . '_field_diff_options_form';
+  if (function_exists($func) && $options_form = $func($type, $settings)) {
+    $form[$variable_name] = $options_form;
+  }
+  $form[$variable_name]['#tree'] = TRUE;
+
+  diff_global_settings_form($form[$variable_name], $form_state, $type, $settings);
+  return system_settings_form($form);
+}
diff --git a/diff.api.php b/diff.api.php
new file mode 100644
index 0000000..507293a
--- /dev/null
+++ b/diff.api.php
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the diff module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Allow modules to provide a comparison about entities.
+ *
+ * @param object $old_entity
+ *   The older entity revision.
+ * @param object $new_entity
+ *   The newer entity revision.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - view_mode: The view mode to use. Defaults to FALSE.
+ *
+ * @return array
+ *   An associative array of values keyed by the entity property.
+ *
+ * @todo
+ *   Investiagate options and document these.
+ */
+function hook_entity_diff($old_entity, $new_entity, $context) {
+  if ($context['entity_type'] == 'node') {
+    $type = node_type_get_type($new_entity);
+    $result['title'] = array(
+      '#name' => $type->title_label,
+      '#old' => array($old_entity->title),
+      '#new' => array($new_entity->title),
+      '#weight' => -5,
+      '#settings' => array(
+        'show_header' => FALSE,
+      ),
+    );
+  }
+}
+
+/**
+ * Allow modules to alter a comparison about entities.
+ *
+ * @param array $entity_diffs
+ *   An array of entity differences.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - old_entity: The older entity.
+ *   - new_entity: The newer entity.
+ *   - view_mode: The view mode to use. Defaults to FALSE.
+ *
+ * @see hook_entity_diff()
+ */
+function hook_entity_diff_alter($entity_diffs, $context) {
+}
+
+/**
+ * Callback to the module that defined the field to prepare items comparison.
+ *
+ * This allows the module to alter all items prior to rendering the comparative
+ * values. It is mainly used to bulk load entities to reduce overheads
+ * associated with loading entities individually.
+ *
+ * @param array $old_items
+ *   An array of field items from the older revision.
+ * @param array $new_items
+ *   An array of field items from the newer revision.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - bundle: The bundle name.
+ *   - field: The field that the items belong to.
+ *   - instance: The instance that the items belong to.
+ *   - language: The language associated with $items.
+ *   - old_entity: The older entity.
+ *   - new_entity: The newer entity.
+ *
+ * @see MODULE_field_diff_view()
+ */
+function MODULE_field_diff_view_prepare(&$old_items, &$new_items, $context) {
+  $fids = array();
+  foreach (array_merge_recursive($old_items, $new_items) as $info) {
+    $fids[$info['fid']] = $info['fid'];
+  }
+  // A single load is much faster than individual loads.
+  $files = file_load_multiple($fids);
+
+  // For ease of processing, store a reference of the entity on the item array.
+  foreach ($old_items as $delta => $info) {
+    $old_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
+  }
+  foreach ($new_items as $delta => $info) {
+    $new_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
+  }
+}
+
+/**
+ * Callback to the module that defined the field to generate items comparisons.
+ *
+ * @param array $items
+ *   An array of field items from the entity.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity: The entity being compared.
+ *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - bundle: The bundle name.
+ *   - field: The field that the items belong to.
+ *   - instance: The instance that the items belong to.
+ *   - language: The language associated with $items.
+ *   - old_entity: The older entity.
+ *   - new_entity: The newer entity.
+ *
+ * @see MODULE_field_diff_view_prepare()
+ */
+function MODULE_field_diff_view($items, $context) {
+  $diff_items = array();
+  foreach ($items as $delta => $item) {
+    if (isset($item['file'])) {
+      $diff_items[$delta] = $item['file']->filename . ' [fid: ' . $item['fid'] . ']';
+    }
+  }
+
+  return $diff_items;
+}
+
+/**
+ * Allow other modules to interact with MODULE_field_diff_view_prepare().
+ *
+ * @param array $old_items
+ *   An array of field items from the older revision.
+ * @param array $new_items
+ *   An array of field items from the newer revision.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - bundle: The bundle name.
+ *   - field: The field that the items belong to.
+ *   - instance: The instance that the items belong to.
+ *   - language: The language associated with $items.
+ *   - old_entity: The older entity.
+ *   - new_entity: The newer entity.
+ *
+ * @see MODULE_field_diff_view_prepare()
+ */
+function hook_field_diff_view_prepare_alter($old_items, $new_items, $context) {
+
+}
+
+/**
+ * Allow other modules to interact with MODULE_field_diff_view().
+ *
+ * @param array $values
+ *   An array of field items from the entity ready for comparison.
+ * @param array $items
+ *   An array of field items from the entity.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity: The entity being compared.
+ *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - bundle: The bundle name.
+ *   - field: The field that the items belong to.
+ *   - instance: The instance that the items belong to.
+ *   - language: The language associated with $items.
+ *   - old_entity: The older entity.
+ *   - new_entity: The newer entity.
+ *
+ * @see MODULE_field_diff_view()
+ */
+function hook_field_diff_view_alter($values, $items, $context) {
+
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/diff.diff.inc b/diff.diff.inc
new file mode 100644
index 0000000..35d29a6
--- /dev/null
+++ b/diff.diff.inc
@@ -0,0 +1,414 @@
+<?php
+
+/**
+ * @file
+ * Includes the hooks defined by diff_hook_info().
+ */
+
+/**
+ * Implements hook_entity_diff().
+ *
+ * Helper function to invoke the depreciated hook_diff() for node entities.
+ *
+ * This manually invokes hook_diff() to avoid a function name clash with the
+ * PHP 5 (>= 5.3.0) date_diff() function or the Dates modules implementation.
+ *
+ * @param object $old_entity
+ *   The older node revision.
+ * @param object $new_entity
+ *   The newer node revision.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - old_entity: The older entity.
+ *   - new_entity: The newer entity.
+ *   - view_mode: The view mode to use. Defaults to FALSE.
+ */
+function diff_entity_diff($old_entity, $new_entity, $context) {
+  $return = array();
+
+  $entity_type = $context['entity_type'];
+  $info = entity_get_info($entity_type);
+  if (!empty($info['fieldable'])) {
+    $return = diff_entity_fields_diff($old_entity, $new_entity, $context);
+  }
+
+  return $return;
+}
+
+/**
+ * Internal callback to handle fieldable entities.
+ *
+ * Field comparison is handled for core modules, but is expandable to any other
+ * fields if the module defines MODULE_field_diff_view().
+ *
+ * @param object $old_entity
+ *   The older entity entity revision.
+ * @param object $new_entity
+ *   The newer entity entity revision.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - old_entity: The older entity.
+ *   - new_entity: The newer entity.
+ *   - view_mode: The view mode to use. Defaults to FALSE.
+ *
+ * @return array
+ *   An associative array of values keyed by the field name and delta value.
+ */
+function diff_entity_fields_diff($old_entity, $new_entity, $context) {
+  $result = array();
+
+  $entity_type = $context['entity_type'];
+  $view_mode = $context['view_mode'];
+
+  $field_context = $context;
+
+  // Custom settings must be checked to prevent pulling out view mode settings
+  // for Diff standard / complete if not enabled.
+  $actual_mode = FALSE;
+  list(,, $bundle_name) = entity_extract_ids($entity_type, $new_entity);
+  $instances = field_info_instances($entity_type, $bundle_name);
+  if (!empty($view_mode)) {
+    $view_mode_settings = field_view_mode_settings($entity_type, $bundle_name);
+    $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings'])) ? $view_mode : 'default';
+  }
+  $field_context['custom_settings'] = $actual_mode && $actual_mode == $view_mode;
+  $field_context['old_entity'] = $old_entity;
+  $field_context['new_entity'] = $new_entity;
+  $field_context['bundle_name'] = $bundle_name;
+
+  foreach ($instances as $instance) {
+    // Any view mode is supported in relation to hiding fields, but only if
+    // selected (todo see if this is a valid option).
+    if ($actual_mode && $instance['display'][$actual_mode]['type'] == 'hidden') {
+      continue;
+    }
+    $field_name = $instance['field_name'];
+    $field = field_info_field($field_name);
+    $field_context['field'] = $field;
+    $field_context['instance'] = $instance;
+    $field_context['display'] = $instance['display'][$actual_mode];
+
+    // We provide a loose check on the field access.
+    if (field_access('view', $field, $entity_type) || field_access('edit', $field, $entity_type)) {
+      $langcode = field_language($entity_type, $new_entity, $field_name);
+
+      $field_context['language'] = $langcode;
+      $field_context['field'] = $field;
+      $field_context['instance'] = $instance;
+
+      $old_items = array();
+      if (!empty($old_entity->{$field_name}[$langcode])) {
+        $old_items = $old_entity->{$field_name}[$langcode];
+      }
+
+      $new_items = array();
+      if (!empty($new_entity->{$field_name}[$langcode])) {
+        $new_items = $new_entity->{$field_name}[$langcode];
+      }
+
+      // Load files containing the field callbacks.
+      _diff_autoload($field);
+
+      $field_context['settings'] = diff_get_field_instance_settings($field_context);
+
+      // Reference fields can optionally prepare objects in bulk to reduce
+      // overheads related to multiple database calls. If a field considers
+      // that the delta values is meaningless, they can order and rearrange
+      // to provide cleaner results.
+      $func = $field['module'] . '_field_diff_view_prepare';
+      if (function_exists($func)) {
+        $func = $func($old_items, $new_items, $field_context);
+      }
+      // Allow other modules to act safely on behalf of the core field module.
+      drupal_alter('field_diff_view_prepare', $old_items, $new_items, $field_context);
+
+      // These functions compiles the items into comparable arrays of strings.
+      $func = $field['module'] . '_field_diff_view';
+      if (!function_exists($func)) {
+        $func = 'diff_field_diff_view';
+      }
+
+      // These callbacks should be independent of revision.
+      $old_context = $field_context;
+      $old_context['entity'] = $old_entity;
+      $old_values = $func($old_items, $old_context);
+      $new_context = $field_context;
+      $new_context['entity'] = $new_entity;
+      $new_values = $func($new_items, $new_context);
+
+      // Allow other modules to act safely on behalf of the core field module.
+      drupal_alter('field_diff_view', $old_values, $old_items, $old_context);
+      drupal_alter('field_diff_view', $new_values, $new_items, $new_context);
+
+      $max = max(array(count($old_values), count($new_values)));
+      if ($max) {
+        $result[$field_name] = array(
+          '#name' => $instance['label'],
+          '#old' => array(),
+          '#new' => array(),
+          '#settings' => $field_context['settings'],
+        );
+        for ($delta = 0; $delta < $max; $delta++) {
+          if (isset($old_values[$delta])) {
+            $result[$field_name]['#old'][] = is_array($old_values[$delta]) ? implode("\n", $old_values[$delta]) : $old_values[$delta];
+          }
+          if (isset($new_values[$delta])) {
+            $result[$field_name]['#new'][] = is_array($new_values[$delta]) ? implode("\n", $new_values[$delta]) : $new_values[$delta];
+          }
+        }
+        $result[$field_name]['#old'] = implode("\n", $result[$field_name]['#old']);
+        $result[$field_name]['#new'] = implode("\n", $result[$field_name]['#new']);
+
+        if ($actual_mode) {
+          $result[$field_name]['#weight'] = $instance['display'][$actual_mode]['weight'];
+        }
+
+      }
+    }
+  }
+  return $result;
+}
+
+/**
+ * A generic handler for parsing field values.
+ *
+ * This callback can only handle the most basic of fields that populates the
+ * safe_value during field load or use the value column for data storage.
+ *
+ * @param array $items
+ *   An array of field items.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity: The entity that the items belong to.
+ *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - bundle: The bundle name.
+ *   - field: The field that the items belong to.
+ *   - instance: The instance that the items belong to.
+ *   - language: The language associated with $items.
+ *   - old_entity: The older entity.
+ *   - new_entity: The newer entity.
+ *
+ * @return array
+ *   An array of strings representing the value, keyed by delta index.
+ */
+function diff_field_diff_view($items, $context) {
+  $diff_items = array();
+  foreach ($items as $delta => $item) {
+    if (isset($item['safe_value'])) {
+      if (is_scalar($item['safe_value'])) {
+        $diff_items[$delta] = (string) $item['safe_value'];
+      }
+      else {
+        $diff_items[$delta] = (string) implode("\n", $item['safe_value']);
+      }
+    }
+    elseif (isset($item['value'])) {
+      if (is_scalar($item['value'])) {
+        $diff_items[$delta] = (string) $item['value'];
+      }
+      else {
+        $diff_items[$delta] = (string) implode("\n", $item['value']);
+      }
+    }
+  }
+  return $diff_items;
+}
+
+/**
+ * Helper function to get the settings for a given field or formatter.
+ *
+ * This will parse the field display formatter settings if the view mode is
+ * set to either Diff standard or Diff complete. These then have any new
+ * global settings added, followed by any field default settings.
+ *
+ * @param array $context
+ *   This will get the settings for a field.
+ *   - field (required): The field that the items belong to.
+ *
+ *   And optionally include these to parse the display settings.
+ *   - entity: The entity that we are looking up.
+ *   - instance: The instance that the items belong to.
+ *   - view_mode: The view mode to use. Defaults to FALSE.
+ *
+ * @return array
+ *   The settings for this field or formatter.
+ */
+function diff_get_field_instance_settings($context) {
+  // This only pulls out custom settings for fields that are using the two
+  // diff view modes. This is done to match the UI that only alters these
+  // two view modes.
+  $display_settings = array();
+  if (!empty($context['view_mode'])) {
+    $entity_type = $context['instance']['entity_type'];
+    $bundle = $context['instance']['bundle'];
+    $view_mode_settings = field_view_mode_settings($entity_type, $bundle);
+    $actual_mode = (!empty($view_mode_settings[$context['view_mode']]['custom_settings'])) ? $view_mode : 'default';
+    if (diff_supported_view_mode($actual_mode)) {
+      // Should we directly use the instance settings or the hooked version?
+      // $display = $context['instance']['display'][$actual_mode];
+      $display = field_get_display($context['instance'], $context['view_mode'], $context['entity']);
+      $display_settings = $display['settings'];
+    }
+  }
+  $display_settings += array(
+    'diff_custom' => 0,
+    'diff_custom_settings' => array(),
+  );
+
+  $field = $context['field'];
+
+  // Load files containing the field callbacks.
+  _diff_autoload($field);
+
+  // Update saved settings from the global settings for this field type.
+  $settings = variable_get("diff_{$field['module']}_field_{$field['type']}_default_options", array());
+  if (!empty($display_settings['diff_custom'])) {
+    // Merge any additional global settings into the field formatter settings.
+    $display_settings['diff_custom_settings'] += $settings;
+    $settings = $display_settings['diff_custom_settings'];
+  }
+  $settings = _diff_field_default_settings($field['module'], $field['type'], $settings);
+
+  return $settings;
+}
+
+/**
+ * Helper function to initiate any global form elements.
+ */
+function diff_global_settings_form(&$subform, $form_state, $type, $settings) {
+  $subform['show_header'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Show field title'),
+    '#default_value' => $settings['show_header'],
+    '#weight' => -5,
+  );
+  $subform['markdown'] = array(
+    '#type' => 'select',
+    '#title' => t('Markdown callback'),
+    '#default_value' => $settings['markdown'],
+    '#options' => array(
+      'drupal_html_to_text' => t('Drupal HTML to Text'),
+      'filter_xss' => t('Filter XSS (some tags)'),
+      'diff_filter_xss' => t('Filter XSS (all tags)'),
+    ),
+    '#description' => t('These provide ways to clean markup tags to make comparisons easier to read.'),
+    '#empty_option' => t('- Do not process -'),
+  );
+  $subform['line_counter'] = array(
+    '#type' => 'radios',
+    '#title' => t('Line counter'),
+    '#default_value' => $settings['line_counter'],
+    '#description' => t('This outputs the (approximate) line numbers as a heading before every change.'),
+    '#options' => array(
+      '' => t('None. Counter ignore and not incremented.'),
+      'hidden' => t('Count lines but do not show line headers.'),
+      'line' => t('Count and show lines, restarting counter at 0.'),
+      'line_continuous' => t('Count and show lines, incrementing counter from last item.'),
+    ),
+  );
+
+  /*
+This would be cool, but to do anything else than inline with the text appears
+to be very hard, requiring a refactoring of both the modules API but also the
+DiffFormatter and Diff classes. Diff 8.x-4.x maybe.
+
+  $subform['show_delta'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Show delta values'),
+    '#default_value' => $settings['show_delta'],
+  );
+  $subform['delta_format'] = array(
+    '#type' => 'radios',
+    '#title' => t('Delta insertion method'),
+    '#default_value' => $settings['delta_format'],
+    '#options' => array(
+      'inline' => t('Prefix to item'),
+      'row' => t('Individual row'),
+    ),
+    '#states' => array(
+      'invisible' => array(
+        "input[id$='show-delta']" => array('checked' => FALSE),
+      ),
+    ),
+  );
+  */
+}
+
+/**
+ * Helper function to populate the settings array.
+ */
+function _diff_field_default_settings($module, $field_type, $settings = array()) {
+  // Load files containing the field callbacks.
+  _diff_autoload($module);
+
+  // Populate any missing values from CALLBACK_field_diff_default_options().
+  $func = $module . '_field_diff_default_options';
+  if (function_exists($func)) {
+    $settings += $func($field_type);
+  }
+
+  // Global settings.
+  $settings += array(
+    'markdown' => '',
+    'line_counter' => '',
+    'show_header' => 1,
+    // Can we? This seems too hard to track in the DiffFormatter as all we
+    // have is a string or an array of strings.
+    //'show_delta' => 0,
+    //'delta_format' => 'row',
+  );
+  return $settings;
+}
+
+/**
+ * Private helper function to load field includes.
+ *
+ * @param array|string $field_or_module
+ *   The field definition array or the module that implements the field.
+ */
+function _diff_autoload($field_or_module) {
+  $includes = &drupal_static(__FUNCTION__, FALSE);
+  if (!$includes) {
+    $includes = array(
+      'file' => module_exists('file'),
+      'image' => module_exists('image'),
+      'list' => module_exists('list'),
+      'taxonomy' => module_exists('taxonomy'),
+      'text' => module_exists('text'),
+    );
+  }
+
+  $module = is_string($field_or_module) ? $field_or_module : $field_or_module['module'];
+
+  // Since field hooks are not real hooks, we manually load the field modules
+  // MODULE.diff.inc. We handle the five core field defining modules.
+  if (!isset($includes[$module])) {
+    module_load_include('diff.inc', $module);
+    $includes[$module] = 0;
+  }
+  elseif (!empty($includes[$module])) {
+    module_load_include('inc', 'diff', 'includes/' . $module);
+    $includes[$module] = 0;
+  }
+}
+
+/**
+ * Helper function to parse out the state in the diff results.
+ */
+function diff_extract_state($diff, $state = 'raw') {
+  $states = array(
+    0 => NULL,
+    1 => NULL,
+  );
+  if (isset($diff['#states'][$state])) {
+    if (isset($diff['#states'][$state]['#old'])) {
+      $states[0] = $diff['#states'][$state]['#old'];
+    }
+    if (isset($diff['#states'][$state]['#new'])) {
+      $states[1] = $diff['#states'][$state]['#new'];
+    }
+  }
+  return $states;
+}
diff --git a/diff.info b/diff.info
index fe26242..c62ccaa 100644
--- a/diff.info
+++ b/diff.info
@@ -3,3 +3,7 @@ description = Show difference between node revisions.
 core = 7.x
 
 files[] = DiffEngine.php
+
+; This would support Module supports if it ever gets ported.
+; The module Field formatter settings enables field formatter alter hooks.
+recommends[] = field_formatter_settings
diff --git a/diff.install b/diff.install
new file mode 100644
index 0000000..e40e2d0
--- /dev/null
+++ b/diff.install
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Provides uninstallation functions.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function diff_uninstall() {
+  // Bulk delete entity based variables.
+  $prefixes = array(
+    'diff_enable_revisions_page_',
+    'diff_show_',
+    'diff_show_preview_changes_',
+    'diff_view_mode_',
+    'diff_admin_path_',
+    'diff_default_state_',
+  );
+  foreach ($prefixes as $prefix) {
+    db_delete('variable')
+      ->condition('name', db_like($prefix) . '%', 'LIKE')
+      ->execute();
+  }
+
+  // Delete global variables.
+  variable_del('diff_trailing_context_lines');
+  variable_del('diff_leading_context_lines');
+  variable_del('diff_theme');
+
+  variable_del('diff_file_field_default_options');
+  variable_del('diff_image_field_default_options');
+  variable_del('diff_taxonomy_field_default_options');
+  variable_del('diff_list_field_default_options');
+  variable_del('diff_text_field_text_default_options');
+  variable_del('diff_text_field_text_long_default_options');
+  variable_del('diff_text_field_text_with_summary_default_options');
+}
+
+/**
+ * Updates the existing system variables to target the entity type and bundle.
+ */
+function diff_update_7201() {
+  $node_types = array_keys(node_type_get_types());
+  foreach ($node_types as $bundle) {
+    $type_variables = array(
+      'show_preview_changes',
+      'enable_revisions_page',
+      'show_diff_inline',
+    );
+    foreach ($type_variables as $prefix) {
+      $setting = variable_get($prefix . '_' . $bundle, NULL);
+      if (isset($setting)) {
+        variable_del($prefix . '_' . $bundle);
+        variable_set('diff_' . $prefix . '_node_' . $bundle, $setting);
+      }
+    }
+  }
+}
+
+/**
+ * This checks that the right display view mode is selected.
+ */
+function diff_update_7202() {
+  $node_types = array_keys(node_type_get_types());
+  foreach ($node_types as $bundle) {
+    // For each type we look for a custom full view mode. If found, we need to
+    // set the diff view modes to this to ensure that there is minimal change
+    // in the front end functionality.
+    $view_mode_settings = field_view_mode_settings('node', $bundle);
+    if (!empty($view_mode_settings['full']['custom_settings'])) {
+      variable_set('diff_view_mode_inline_node_' . $node_type, 'full');
+    }
+  }
+}
+
+/**
+ * Register new administration menu items.
+ */
+function diff_update_7203() {
+  variable_set('menu_rebuild_needed', TRUE);
+  variable_set('diff_script_revisioning', '');
+}
diff --git a/diff.module b/diff.module
index a9c200a..d8d4d3b 100644
--- a/diff.module
+++ b/diff.module
@@ -11,6 +11,27 @@
 define('REVISION_LIST_SIZE', 50);
 
 /**
+ * Exposed sorting options.
+ *
+ * No sorting means sorting by delta value for fields.
+ */
+define('DIFF_SORT_NONE', '0');
+
+/**
+ * Exposed sorting options.
+ *
+ * This normally sorts by the rendered comparison.
+ */
+define('DIFF_SORT_VALUE', '1');
+
+/**
+ * Exposed sorting options.
+ *
+ * It is up to the field / entity to decide how to handle the sort.
+ */
+define('DIFF_SORT_CUSTOM', '-1');
+
+/**
  * Implements hook_help().
  */
 function diff_help($path, $arg) {
@@ -19,7 +40,7 @@ function diff_help($path, $arg) {
       $output = '<p>' . t('The diff module overwrites the normal revisions view. The revisions table is enhanced with a possibility to view the difference between two node revisions. Users with the %view_revisions permission will also be able to view the changes between any two selected revisions. You may disable this for individual content types on the content type configuration page. This module also provides a nifty %preview_changes button while editing a post.', array('%preview_changes' => t('View changes'), '%view_revisions' => t('view revisions'))) . '</p>';
       return $output;
     case 'node/%/revisions/%/view':
-      // the following string is copied from string copied from node_help('node/%/revisions')
+      // The translated strings should match node_help('node/%/revisions').
       return '<p>' . t('The revisions let you track differences between multiple versions of a post.') . '</p>';
     case 'node/%/revisions/view/%/%':
       return '<p>' . t('Comparing two revisions:') . '</p>';
@@ -27,24 +48,41 @@ function diff_help($path, $arg) {
 }
 
 /**
+ * The various states that are available.
+ */
+function diff_avaiable_states($entity_type = NULL) {
+  $states = array(
+    'raw' => t('Standard'),
+    'raw_plain' => t('Marked down'),
+  );
+
+  return $states;
+}
+
+/**
  * Implements hook_menu().
+ *
+ * @todo: Review this.
  */
 function diff_menu() {
   $items = array();
 
   /**
-   * By using MENU_LOCAL_TASK (and 'tab_parent') we can get the various revision-views to
-   * show the View|Edit|Revision-tabs of the node on top, and have the Revisions-tab open.
-   * To avoid creating/showing any extra tabs or sub-tabs (tasks below top level) for the
-   * various paths (i.e. "Diff", "Show latest" and "Show a specific revision") that need
-   * a revision-id (vid) parameter, we make sure to set 'tab_parent' a bit odd.
-   * This solution may not be the prettiest one, but by avoiding having two _LOCAL_TASKs
-   * sharing a parent that can be accessed by its full path, it seems to work as desired.
-   * Breadcrumbs work decently, at least the node link is among the crumbs. For some reason
-   * any breadcrumbs "before/above" the node is only seen at 'node/%node/revisions/%/view'.
+   * By using MENU_LOCAL_TASK (and 'tab_parent') we can get the various
+   * revision-views to show the View|Edit|Revision-tabs of the node on top,
+   * and have the Revisions-tab open. To avoid creating/showing any extra tabs
+   * or sub-tabs (tasks below top level) for the various paths (i.e. "Diff",
+   * "Show latest" and "Show a specific revision") that need a revision-id (vid)
+   * parameter, we make sure to set 'tab_parent' a bit odd. This solution may
+   * not be the prettiest one, but by avoiding having two _LOCAL_TASKs sharing
+   * a parent that can be accessed by its full path, it seems to work as
+   * desired. Breadcrumbs work decently, at least the node link is among the
+   * crumbs. For some reason any breadcrumbs "before/above" the node is only
+   * seen at 'node/%node/revisions/%/view'.
    */
+
+  // Not used directly, but was created to get the other menu items to work.
   $items['node/%node/revisions/list'] = array(
-    // Not used directly, but was created to get the other menu items to work well
     'title' => 'List revisions',
     'page callback' => 'diff_diffs_overview',
     'type' => MENU_DEFAULT_LOCAL_TASK,
@@ -62,6 +100,7 @@ function diff_menu() {
     'tab_parent' => 'node/%/revisions/list',
     'file' => 'diff.pages.inc',
   );
+
   $items['node/%node/revisions/view/latest'] = array(
     'title' => 'Show latest diff',
     'page callback' => 'diff_latest',
@@ -72,6 +111,72 @@ function diff_menu() {
     'tab_parent' => 'node/%/revisions/view',
     'file' => 'diff.pages.inc',
   );
+
+  // Administrative settings.
+  $items['admin/config/content/diff'] = array(
+    'title' => 'Diff',
+    'description' => 'Diff settings.',
+    'file' => 'diff.admin.inc',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('diff_admin_settings'),
+    'access arguments' => array('administer site configuration'),
+  );
+  $items['admin/config/content/diff/settings'] = array(
+    'title' => 'Settings',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/config/content/diff/fields'] = array(
+    'title' => 'Fields',
+    'description' => 'Field support and settings overview.',
+    'file' => 'diff.admin.inc',
+    'page callback' => 'diff_admin_field_overview',
+    'access arguments' => array('administer site configuration'),
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['admin/config/content/diff/fields/%'] = array(
+    'title' => 'Global field settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('diff_admin_global_field_settings', 5),
+    'access arguments' => array('administer site configuration'),
+    'type' => MENU_VISIBLE_IN_BREADCRUMB,
+    'file' => 'diff.admin.inc',
+  );
+
+  $items['admin/config/content/diff/entities'] = array(
+    'title' => 'Entities',
+    'description' => 'Entity settings.',
+    'file' => 'diff.admin.inc',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('diff_admin_global_entity_settings', 'node'),
+    'access arguments' => array('administer site configuration'),
+    'type' => MENU_LOCAL_TASK,
+  );
+
+  $items['admin/config/content/diff/entities/node'] = array(
+    'title' => 'Node',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+
+  // Safely restriction from core Field UI module.
+  if (defined('MAINTENANCE_MODE')) {
+    return $items;
+  }
+  // Create tabs for all entities.
+  foreach (entity_get_info() as $entity_type => $entity_info) {
+    if ($entity_type == 'node') {
+      continue;
+    }
+    $items['admin/config/content/diff/entities/' . $entity_type] = array(
+      'title' => $entity_info['label'],
+      'file' => 'diff.admin.inc',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('diff_admin_global_entity_settings', $entity_type),
+      'access arguments' => array('administer site configuration'),
+      'type' => MENU_LOCAL_TASK,
+    );
+  }
   return $items;
 }
 
@@ -79,7 +184,7 @@ function diff_menu() {
  * Implements hook_menu_alter().
  */
 function diff_menu_alter(&$callbacks) {
-  // Overwrite the default 'Revisions' page
+  // Overwrite the default 'Revisions' page.
   $callbacks['node/%node/revisions']['page callback'] = 'diff_diffs_overview';
   $callbacks['node/%node/revisions']['module'] = 'diff';
   $callbacks['node/%node/revisions']['file'] = 'diff.pages.inc';
@@ -88,30 +193,78 @@ function diff_menu_alter(&$callbacks) {
   $callbacks['node/%node/revisions/%/revert']['tab_parent'] = 'node/%/revisions/%/view';
   $callbacks['node/%node/revisions/%/delete']['tab_parent'] = 'node/%/revisions/%/view';
 
-  $callbacks['node/%node/revisions']['access callback'] =
-  $callbacks['node/%node/revisions/%/view']['access callback'] =
-  $callbacks['node/%node/revisions/%/revert']['access callback'] =
-  $callbacks['node/%node/revisions/%/delete']['access callback'] = 'diff_node_revision_access';
-  return;
+  $callbacks['node/%node/revisions']['access callback']
+      = $callbacks['node/%node/revisions/%/view']['access callback']
+      = $callbacks['node/%node/revisions/%/revert']['access callback']
+      = $callbacks['node/%node/revisions/%/delete']['access callback'] = 'diff_node_revision_access';
 }
 
 /**
  * Implements hook_admin_paths_alter().
  */
 function diff_admin_paths_alter(&$paths) {
-  // Treat all user pages as administrative.
-  $paths['node/*/revisions/view/*/*'] = TRUE;
+  // By default, treat all diff pages as administrative.
+  if (variable_get('diff_admin_path_node', 1)) {
+    $paths['node/*/revisions/view/*/*'] = TRUE;
+  }
 }
 
 /**
  * Access callback for the node revisions page.
  */
 function diff_node_revision_access($node, $op = 'view') {
-  $may_revision_this_type = variable_get('enable_revisions_page_' . $node->type, TRUE) || user_access('administer nodes');
+  $may_revision_this_type = variable_get('diff_enable_revisions_page_node_' . $node->type, TRUE) || user_access('administer nodes');
   return $may_revision_this_type && _node_revision_access($node, $op);
 }
 
 /**
+ * Implements hook_hook_info().
+ */
+function diff_hook_info() {
+  $hooks['entity_diff'] = array(
+    'group' => 'diff',
+  );
+  $hooks['diff'] = array(
+    'group' => 'diff',
+  );
+  $hooks['field_diff_view_prepare_alter'] = array(
+    'group' => 'diff',
+  );
+  $hooks['field_diff_view_alter'] = array(
+    'group' => 'diff',
+  );
+
+  return $hooks;
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ *
+ * Although the module only provides an UI for comparing nodes, it has an
+ * extendable API for any entity, so we supply two view modes for all entities.
+ * - diff_standard: This view mode is used to tell the module how to compare
+ *                  individual fields. This is used on the revisions page.
+ * - diff_complete: The view mode is used to compare the entire entity as a
+ *                  single rendered string. This is used by the inline block.
+ */
+function diff_entity_info_alter(&$entity_info) {
+  foreach (array_keys($entity_info) as $entity_type) {
+    if (!empty($entity_info[$entity_type]['view modes'])) {
+      $entity_info[$entity_type]['view modes'] += array(
+        'diff_standard' => array(
+          'label' => t('Diff standard'),
+          'custom settings' => FALSE,
+        ),
+        'diff_complete' => array(
+          'label' => t('Diff complete'),
+          'custom settings' => FALSE,
+        ),
+      );
+    }
+  }
+}
+
+/**
  * Implements hook_block_info().
  */
 function diff_block_info() {
@@ -134,11 +287,11 @@ function diff_block_view($delta) {
 }
 
 /**
- * Implements hook_nodeapi().
+ * Implements hook_node_view_alter().
  */
 function diff_node_view_alter(&$build) {
   $node = $build['#node'];
-  if (user_access('view revisions') && variable_get('show_diff_inline_' . $node->type, FALSE)) {
+  if (user_access('view revisions') && variable_get('diff_show_diff_inline_node_' . $node->type, FALSE)) {
     // Ugly but cheap way to check that we are viewing a node's revision page.
     if (arg(2) === 'revisions' && arg(3) === $node->vid) {
       module_load_include('inc', 'diff', 'diff.pages');
@@ -152,78 +305,150 @@ function diff_node_view_alter(&$build) {
 }
 
 /**
- * Implements hook_form_alter().
+ * Implements hook_form_BASE_FORM_ID_alter().
  */
-function diff_form_alter(&$form, &$form_state, $form_id) {
-  if (!empty($form['#node_edit_form'])) {
-    // Add a 'View changes' button on the node edit form.
-    if (variable_get('show_preview_changes_' . $form['type']['#value'], TRUE) && $form['nid']['#value'] > 0) {
-      $form['actions']['preview_changes'] = array(
-        '#type' => 'submit',
-        '#value' => t('View changes'),
-        '#weight' => 12,
-        '#submit' => array('diff_node_form_build_preview_changes')
-      );
-    }
+function diff_form_node_form_alter(&$form, $form_state) {
+  // Add a 'View changes' button on the node edit form.
+  $node = $form['#node'];
+  if (variable_get('diff_show_preview_changes_node_' . $node->type, TRUE) && !empty($node->nid)) {
+    $form['actions']['preview_changes'] = array(
+      '#type' => 'submit',
+      '#value' => t('View changes'),
+      '#weight' => 12,
+      '#submit' => array('diff_node_form_build_preview_changes'),
+    );
   }
 }
 
 /**
- * Implements hook_form_alter() for node_type_form.
+ * Implements hook_form_BASE_FORM_ID_alter().
  */
 function diff_form_node_type_form_alter(&$form, $form_state) {
   if (isset($form['type'])) {
-    // Node type edit form.
-    // Add checkbox to activate 'View changes' button per node type.
+    $type = $form['#node_type'];
     $form['diff'] = array(
       '#title' => t('Diff'),
       '#type' => 'fieldset',
       '#group' => 'additional_settings',
       '#tree' => FALSE,
     );
-    $form['diff']['show_preview_changes'] = array(
+    $form['diff']['diff_show_preview_changes_node'] = array(
       '#type' => 'checkbox',
       '#title' => t('Show %preview_changes button on node edit form', array('%preview_changes' => t('View changes'))),
       '#weight' => 10,
-      '#default_value' => variable_get('show_preview_changes_' . $form['#node_type']->type, TRUE),
+      '#default_value' => variable_get('diff_show_preview_changes_node_' . $type->type, TRUE),
     );
-    $form['diff']['remove_markup_default'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Remove markup by default when comparing body text'),
-      '#weight' => 10,
-      '#default_value' => variable_get('remove_markup_default_'. $form['#node_type']->type, FALSE),
-    );
-    $form['diff']['show_diff_inline'] = array(
+    $form['diff']['diff_show_diff_inline_node'] = array(
       '#type' => 'checkbox',
       '#title' => t('Show diffs inline for this content type'),
       '#description' => t("You must enable the 'Inline diff' block to use this feature"),
       '#weight' => 10,
-      '#default_value' => variable_get('show_diff_inline_' . $form['#node_type']->type, FALSE),
+      '#default_value' => variable_get('diff_show_diff_inline_node_' . $type->type, FALSE),
     );
-    $form['diff']['enable_revisions_page'] = array(
+    $form['diff']['diff_enable_revisions_page_node'] = array(
       '#type' => 'checkbox',
       '#title' => t('Enable the %revisions page for this content type', array('%revisions' => t('Revisions'))),
       '#weight' => 11,
-      '#default_value' => variable_get('enable_revisions_page_' . $form['#node_type']->type, TRUE),
+      '#default_value' => variable_get('diff_enable_revisions_page_node_' . $type->type, TRUE),
+    );
+    $form['diff']['diff_view_mode_standard_node'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Use %view_mode view mode when doing standard field comparisons', array('%view_mode' => t('Diff standard'))),
+      '#description' => t('<p>You need to enable this view mode in the @type_label types %custom_display_settings to control settings other than just what fields are shown. Once enabled you can configure many of the diff options used to compare these fields.</p><p><em>If it is not selected, all compatable fields will be compared using default settings.</em></p>', array(
+        '@type_label' => $type->name,
+        '%custom_display_settings' => t('Custom display settings'),
+      )),
+      '#weight' => 12,
+      '#default_value' => variable_get('diff_view_mode_standard_node_' . $type->type, FALSE),
+    );
+    $options = array();
+    $info = entity_get_info('node');
+    foreach ($info['view modes'] as $view_mode => $view_mode_info) {
+      $options[$view_mode] = $view_mode_info['label'];
+    }
+    $form['diff']['diff_view_mode_preview_node'] = array(
+      '#type' => 'select',
+      '#title' => t('Standard comparison preview'),
+      '#description' => t('This governs the %current view mode when doing standard comparisons.', array(
+        '%current' => t('Current revision:'),
+      )),
+      '#options' => $options,
+      '#weight' => 13,
+      '#default_value' => variable_get('diff_view_mode_preview_node_' . $type->type, 'full'),
+      '#empty_value' => '',
+      '#empty_option' => t('- Do not display -'),
+    );
+    $form['diff']['diff_view_mode_inline_node'] = array(
+      '#type' => 'select',
+      '#title' => t('Inline diff view mode'),
+      '#description' => t('This governs what view mode is used to compare and display content in this block.'),
+      '#options' => $options,
+      '#weight' => 14,
+      '#default_value' => variable_get('diff_view_mode_inline_node_' . $type->type, 'diff_complete'),
     );
   }
 }
 
 /**
- * Callback if 'View changes' is pressed.
+ * Implements hook_node_type_update().
+ *
+ * This tracks the diff settings in case the node content type is renamed.
+ */
+function diff_node_type_update($info) {
+  if (!empty($info->old_type) && $info->old_type != $info->type) {
+    $type_variables = array(
+      'diff_show_preview_changes_node',
+      'diff_enable_revisions_page_node',
+      'diff_show_diff_inline_node',
+      'diff_view_mode_standard_node',
+      'diff_view_mode_inline_node',
+      'diff_view_mode_preview_node',
+    );
+    foreach ($type_variables as $prefix) {
+      $setting = variable_get($prefix . '_' . $info->old_type, NULL);
+      if (isset($setting)) {
+        variable_del($prefix . '_' . $info->old_type);
+        variable_set($prefix . '_' . $info->type, $setting);
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_node_type_delete().
+ */
+function diff_node_type_delete($info) {
+  variable_del('diff_show_preview_changes_node_' . $info->type);
+  variable_del('diff_enable_revisions_page_node_' . $info->type);
+  variable_del('diff_show_diff_inline_node_' . $info->type);
+  variable_del('diff_view_mode_standard_node_' . $info->type);
+  variable_del('diff_view_mode_inline_node_' . $info->type);
+  variable_del('diff_view_mode_preview_node_' . $info->type);
+}
+
+/**
+ * Submit handler for the 'View changes' action.
+ *
+ * @see node_form_build_preview()
  */
 function diff_node_form_build_preview_changes($form, &$form_state) {
   module_load_include('inc', 'diff', 'diff.pages');
   $old_node = clone node_load($form_state['values']['nid']);
   $node = node_form_submit_build_node($form, $form_state);
 
-  // Create diff of old node and edited node
-  $rows = _diff_body_rows($old_node, $node, variable_get('remove_markup_default_'. $node->type, FALSE));
-  $cols = _diff_default_cols();
-  $header = _diff_default_header();
-  $changes = theme('diff_table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => 'diff'), 'cols' => $cols));
+  // Create diff of old node and edited node.
+  $rows = _diff_body_rows($old_node, $node);
+
+  $header = _diff_default_header(t('Original'), t('Changes'));
+  $changes = theme('table__diff__preview', array(
+    'header' => $header,
+    'rows' => $rows,
+    'attributes' => array('class' => 'diff'),
+    'colgroups' => _diff_default_cols(),
+    'sticky' => FALSE,
+  ));
 
-  // Prepend diff to edit form
+  // Prepend diff to edit form.
   $form_state['node_preview'] = $changes;
   $form_state['rebuild'] = TRUE;
 }
@@ -237,10 +462,6 @@ function diff_theme() {
       'render element' => 'form',
       'file' => 'diff.theme.inc',
     ),
-    'diff_table' => array(
-      'arguments' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'cols' => array()),
-      'file' => 'diff.theme.inc',
-    ),
     'diff_header_line' => array(
       'arguments' => array('lineno' => NULL),
       'file' => 'diff.theme.inc',
@@ -269,8 +490,7 @@ function diff_theme() {
 }
 
 /**
- * Render a diff of two strings to a $rows array suitable for use with
- * theme('table') or theme('diff_table').
+ * Render the table rows for theme('table').
  *
  * @param string $a
  *   The source string to compare from.
@@ -278,33 +498,42 @@ function diff_theme() {
  *   The target string to compare to.
  * @param boolean $show_header
  *   Display diff context headers, e.g. "Line x".
- * @return
+ * @param array $line_stats
+ *   This structure tracks line numbers across multiple calls to DiffFormatter.
+ *
+ * @return array
  *   Array of rows usable with theme('table').
  */
-function diff_get_rows($a, $b, $show_header = FALSE) {
+function diff_get_rows($a, $b, $show_header = FALSE, &$line_stats = NULL) {
   $a = is_array($a) ? $a : explode("\n", $a);
   $b = is_array($b) ? $b : explode("\n", $b);
 
-  module_load_include('php', 'diff', 'DiffEngine');
+  if (!isset($line_stats)) {
+    $line_stats = array(
+      'counter' => array('x' => 0, 'y' => 0),
+      'offset' => array('x' => 0, 'y' => 0),
+    );
+  }
   $formatter = new DrupalDiffFormatter();
+  // Header is the line counter.
   $formatter->show_header = $show_header;
+  $formatter->line_stats = &$line_stats;
   $diff = new Diff($a, $b);
   return $formatter->format($diff);
 }
 
 /**
- * Render a diff of two strings into HTML markup indicating additions, changes
- * and deletions.
+ * Render and markup a diff of two strings into HTML markup.
  *
  * @param string $a
  *   The source string to compare from.
  * @param string $b
  *   The target string to compare to.
- * @return
+ *
+ * @return string
  *   String containing HTML markup.
  */
 function diff_get_inline($a, $b) {
-  module_load_include('php', 'diff', 'DiffEngine');
   $diff = new DrupalDiffInline($a, $b);
   return $diff->render();
 }
@@ -316,7 +545,7 @@ function diff_inline_form($form, $form_state, $node, $revisions) {
   $form = array();
   $form['node'] = array(
     '#type' => 'value',
-    '#value' => $node
+    '#value' => $node,
   );
   $form['revision'] = array(
     '#type' => 'select',
@@ -363,3 +592,132 @@ function diff_inline_form_submit(&$form, &$form_state) {
     $form_state['redirect'] = "node/{$node->nid}/revisions/{$vid}/view";
   }
 }
+
+/**
+ * A helper function to normalise system differences.
+ *
+ * This handles differences in:
+ * - line endings: Mac, Windows and UNIX all use different line endings.
+ */
+function diff_normalise_text($text) {
+  if (variable_get('diff_normalise_text', 0) && drupal_strlen($text)) {
+    // Convert all Windows and Mac newlines to a single newline, so diff only
+    // need to deal with one possibility.
+    $text = str_replace(array("\r\n", "\r"), "\n", $text);
+  }
+  return $text;
+}
+
+/**
+ * A wrapper function for filter_xss() to exclude all tags.
+ */
+function diff_filter_xss($string) {
+  return filter_xss($string, array());
+}
+
+/**
+ * Implements hook_field_formatter_info_alter().
+ *
+ * This adds the additional settings that are used by the module if the view
+ * mode is used.
+ *
+ * This require the Field formatter settings (field_formatter_settings) module.
+ */
+function diff_field_formatter_info_alter(&$formatters) {
+  foreach ($formatters as $formatter => $info) {
+    $settings = &$formatters[$formatter]['settings'];
+    $settings += array(
+      'diff_custom' => 0,
+      'diff_custom_settings' => array(),
+    );
+  }
+}
+
+/**
+ * Implements hook_field_formatter_settings_form_alter().
+ *
+ * This require the Field formatter settings (field_formatter_settings) module.
+ */
+function diff_field_formatter_settings_form_alter(array &$settings_form, array $context) {
+  if (diff_supported_view_mode($context['view_mode'])) {
+    module_load_include('diff.inc', 'diff');
+    $field = $context['field'];
+    $instance = $context['instance'];
+    $display = $instance['display'][$context['view_mode']];
+    $display['settings'] += array(
+      'diff_custom' => 0,
+      'diff_custom_settings' => array(),
+    );
+
+    $settings_form['diff_custom'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Enable custom Diff settings'),
+      '#default_value' => $display['settings']['diff_custom'],
+    );
+    $settings_form['diff_custom_settings'] = array(
+      '#type' => 'container',
+      '#states' => array(
+        'visible' => array(
+          ':input[name="fields[' . $field['field_name'] . '][settings_edit_form][settings][diff_custom]"]' => array('checked' => TRUE),
+        ),
+      ),
+    );
+
+    // This will handle the autoload for the field module.
+    $diff_settings = diff_get_field_instance_settings($context);
+    $func = $field['module'] . '_field_diff_options_form';
+    if (function_exists($func) && $options_form = $func($field['type'], $diff_settings)) {
+      $settings_form['diff_custom_settings'] += $options_form;
+    }
+    diff_global_settings_form($settings_form['diff_custom_settings'], $form_state, $type, $settings);
+  }
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary_alter().
+ *
+ * This require the Field formatter settings (field_formatter_settings) module.
+ */
+function diff_field_formatter_settings_summary_alter(&$summary, array $context) {
+  if (diff_supported_view_mode($context['view_mode'])) {
+    $field = $context['field'];
+    $display = $context['instance']['display'][$context['view_mode']];
+
+    $summary = empty($summary) ? '' : $summary . '<br />';
+    if (empty($display['settings']['diff_custom'])) {
+      $summary .= t('Diff custom settings: No');
+    }
+    else {
+      $summary .= t('Diff custom settings: Yes');
+    }
+  }
+}
+
+/**
+ * Helper function to check the view mode.
+ */
+function diff_supported_view_mode($view_mode) {
+  return in_array($view_mode, array('diff_standard', 'diff_complete'));
+}
+
+/**
+ * Helper function to load any CSS or JScript files required by a page or form.
+ */
+function diff_build_attachments($jscript = FALSE) {
+  $attachments = array();
+  if ($theme = variable_get('diff_theme', 'default')) {
+    $attachments['css'] = array(
+      drupal_get_path('module', 'diff') . "/css/diff.{$theme}.css",
+    );
+  }
+  if ($jscript && $type = variable_get('diff_script_revisioning', 'simple')) {
+    $attachments['js'] = array(
+      drupal_get_path('module', 'diff') . "/js/diff.js",
+      array(
+        'data' => array('diffRevisionRadios' => $type),
+        'type' => 'setting',
+      ),
+    );
+  }
+  return $attachments;
+}
diff --git a/diff.pages.inc b/diff.pages.inc
index 7304e2c..1803516 100644
--- a/diff.pages.inc
+++ b/diff.pages.inc
@@ -1,5 +1,4 @@
 <?php
-// $Id$
 
 /**
  * @file
@@ -17,6 +16,8 @@ function diff_latest($node) {
 }
 
 /**
+ * Menu callback - an overview table of older revisions.
+ *
  * Generate an overview table of older revisions of a node and provide
  * an input form to select two revisions for a comparison.
  */
@@ -27,14 +28,8 @@ function diff_diffs_overview($node) {
 
 /**
  * Input form to select two revisions.
- *
- * @param $node
- *   Node whose revisions are displayed for selection.
  */
 function diff_node_revisions($form, $form_state, $node) {
-  global $form_values;
-  // $form = array();
-
   $form['nid'] = array(
     '#type' => 'hidden',
     '#value' => $node->nid,
@@ -43,7 +38,8 @@ function diff_node_revisions($form, $form_state, $node) {
   $revision_list = node_revision_list($node);
 
   if (count($revision_list) > REVISION_LIST_SIZE) {
-    // If the list of revisions is longer than the number shown on one page split the array.
+    // If the list of revisions is longer than the number shown on one page
+    // split the array.
     $page = isset($_GET['page']) ? $_GET['page'] : '0';
     $revision_chunks = array_chunk(node_revision_list($node), REVISION_LIST_SIZE);
     $revisions = $revision_chunks[$page];
@@ -52,7 +48,7 @@ function diff_node_revisions($form, $form_state, $node) {
     global $pager_page_array, $pager_total, $pager_total_items;
     $pager_total_items[0] = count($revision_list);
     $pager_total[0] = ceil(count($revision_list) / REVISION_LIST_SIZE);
-    $pager_page_array[0] = max(0, min($page, ((int)$pager_total[0]) - 1));
+    $pager_page_array[0] = max(0, min($page, ((int) $pager_total[0]) - 1));
   }
   else {
     $revisions = $revision_list;
@@ -71,26 +67,31 @@ function diff_node_revisions($form, $form_state, $node) {
     $operations = array();
     $revision_ids[$revision->vid] = '';
 
+    $revision_log = ($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : '';
     if ($revision->current_vid > 0) {
       $form['info'][$revision->vid] = array(
         '#markup' => t('!date by !username', array(
           '!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"),
-          '!username' => theme('username', array('account' => $revision))))
-        . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : ''),
+          '!username' => theme('username', array('account' => $revision)))) . $revision_log,
       );
     }
     else {
+      $diff_date = l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view");
       $form['info'][$revision->vid] = array(
         '#markup' => t('!date by !username', array(
-          '!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"),
-          '!username' => theme('username', array('account' => $revision))))
-        . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : '')
+          '!date' => $diff_date,
+          '!username' => theme('username', array('account' => $revision)))
+        ) . $revision_log,
       );
       if ($revert_permission) {
-        $operations[] = array('#markup' => l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert"));
+        $operations[] = array(
+          '#markup' => l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert"),
+        );
       }
       if ($delete_permission) {
-        $operations[] = array('#markup' => l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete"));
+        $operations[] = array(
+          '#markup' => l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete"),
+        );
       }
       // Set a dummy, even if the user has no permission for the other
       // operations, so that we can check if the operations array is
@@ -106,19 +107,20 @@ function diff_node_revisions($form, $form_state, $node) {
   $form['diff']['old'] = array(
     '#type' => 'radios',
     '#options' => $revision_ids,
-    '#default_value' => $old_vid
+    '#default_value' => $old_vid,
   );
   $form['diff']['new'] = array(
     '#type' => 'radios',
     '#options' => $revision_ids,
-    '#default_value' => $new_vid
+    '#default_value' => $new_vid,
   );
+
   $form['submit'] = array('#type' => 'submit', '#value' => t('Show diff'));
 
   if (count($revision_list) > REVISION_LIST_SIZE) {
     $form['#suffix'] = theme('pager');
   }
-
+  $form['#attached'] = diff_build_attachments(TRUE);
   return $form;
 }
 
@@ -126,10 +128,10 @@ function diff_node_revisions($form, $form_state, $node) {
  * Submit code for input form to select two revisions.
  */
 function diff_node_revisions_submit($form, &$form_state) {
-  // the ids are ordered so the old revision is always on the left
+  // The ids are ordered so the old revision is always on the left.
   $old_vid = min($form_state['values']['old'], $form_state['values']['new']);
   $new_vid = max($form_state['values']['old'], $form_state['values']['new']);
-  $form_state['redirect'] =  'node/' . $form_state['values']['nid'] . '/revisions/view/' . $old_vid . '/' . $new_vid;
+  $form_state['redirect'] = 'node/' . $form_state['values']['nid'] . '/revisions/view/' . $old_vid . '/' . $new_vid;
 }
 
 /**
@@ -138,46 +140,50 @@ function diff_node_revisions_submit($form, &$form_state) {
 function diff_node_revisions_validate($form, &$form_state) {
   $old_vid = $form_state['values']['old'];
   $new_vid = $form_state['values']['new'];
-  if ($old_vid==$new_vid || !$old_vid || !$new_vid) {
+  if ($old_vid == $new_vid || !$old_vid || !$new_vid) {
     form_set_error('diff', t('Select different revisions to compare.'));
   }
 }
 
 /**
- * Create output string for a comparison of 'node' between
- * versions 'old_vid' and 'new_vid'.
+ * Create a comparison for the node between versions 'old_vid' and 'new_vid'.
  *
- * @param $node
+ * @param object $node
  *   Node on which to perform comparison
- * @param $old_vid
+ * @param integer $old_vid
  *   Version ID of the old revision.
- * @param $new_vid
+ * @param integer $new_vid
  *   Version ID of the new revision.
- * @param $remove_markup
- *   Strip markup before doing the diff
  */
-function diff_diffs_show($node, $old_vid, $new_vid, $remove_markup = NULL) {
-
-  // Set same title as on the 'Revisions' tab for consistency
-  drupal_set_title(t('Revisions for @title', array('@title' => $node->title)));
+function diff_diffs_show($node, $old_vid, $new_vid, $state = NULL) {
+  // Attaches the CSS.
+  $build['#attached'] = diff_build_attachments();
+
+  $default_state = variable_get('diff_default_state_node', 'raw');
+  $state = str_replace('-', '_', $state);
+  if ($state && !in_array($state, diff_avaiable_states())) {
+    $states = $default_state;
+  }
+  if (empty($state)) {
+    $state = $default_state;
+  }
 
+  // Same title as the 'Revisions' tab. Blocked by non-page requests.
+  if (node_is_page($node)) {
+    drupal_set_title(t('Revisions for @title', array('@title' => $node->title)));
+  }
   $node_revisions = node_revision_list($node);
 
   $old_node = node_load($node->nid, $old_vid);
   $new_node = node_load($node->nid, $new_vid);
 
-  // Set default for remove markup if not set
-  if ($remove_markup == NULL) {
-    $remove_markup = variable_get('remove_markup_default_'. $node->type, FALSE);
-  }
-
-  // Generate table header (date, username, logmessage).
+  // Generate table header (date, username, log message).
   $old_header = t('!date by !username', array(
-    '!date' => l(format_date($old_node->revision_timestamp), "node/$node->nid/revisions/$old_node->vid/view"),
+    '!date' => l(format_date($old_node->revision_timestamp), "node/$node->nid/revisions/$old_node->vid/view", array('absolute' => 1)),
     '!username' => theme('username', array('account' => $node_revisions[$old_vid])),
   ));
   $new_header = t('!date by !username', array(
-    '!date' => l(format_date($new_node->revision_timestamp), "node/$node->nid/revisions/$new_node->vid/view"),
+    '!date' => l(format_date($new_node->revision_timestamp), "node/$node->nid/revisions/$new_node->vid/view", array('absolute' => 1)),
     '!username' => theme('username', array('account' => $node_revisions[$new_vid])),
   ));
 
@@ -185,143 +191,199 @@ function diff_diffs_show($node, $old_vid, $new_vid, $remove_markup = NULL) {
   $new_log = $new_node->log != '' ? '<p class="revision-log">' . filter_xss($new_node->log) . '</p>' : '';
 
   // Generate previous diff/next diff links.
+  $nav_suffix = ($default_state != $state) ? '/' . str_replace('_', '-', $state) : '';
   $next_vid = _diff_get_next_vid($node_revisions, $new_vid);
   if ($next_vid) {
-    $next_link = l(t('next diff >'), 'node/' . $node->nid . '/revisions/view/' . $new_vid . '/' . $next_vid);
+    $next_link = l(t('next diff >'), 'node/' . $node->nid . '/revisions/view/' . $new_vid . '/' . $next_vid . $nav_suffix, array('absolute' => 1));
   }
   else {
     $next_link = '';
   }
   $prev_vid = _diff_get_previous_vid($node_revisions, $old_vid);
   if ($prev_vid) {
-    $prev_link = l(t('< previous diff'), 'node/' . $node->nid . '/revisions/view/' . $prev_vid . '/' . $old_vid);
+    $prev_link = l(t('< previous diff'), 'node/' . $node->nid . '/revisions/view/' . $prev_vid . '/' . $old_vid . $nav_suffix, array('absolute' => 1));
   }
   else {
     $prev_link = '';
   }
 
-  $cols = _diff_default_cols();
   $header = _diff_default_header($old_header, $new_header);
   $rows = array();
   if ($old_log || $new_log) {
-    $rows[] = array(
+    $rows['logs'] = array(
       array(
         'data' => $old_log,
-        'colspan' => 2
+        'colspan' => 2,
       ),
       array(
         'data' => $new_log,
-        'colspan' => 2
-      )
+        'colspan' => 2,
+      ),
     );
   }
-  $rows[] = array(
+  $rows['navigation'] = array(
     array(
       'data' => $prev_link,
       'class' => array('diff-prevlink'),
-      'colspan' => 2
+      'colspan' => 2,
     ),
     array(
       'data' => $next_link,
       'class' => array('diff-nextlink'),
-      'colspan' => 2
-    )
+      'colspan' => 2,
+    ),
   );
 
-  if ($remove_markup) {
-    $show_hide_markup_link= l(t('Show Markup'), 'node/'. $node->nid .'/revisions/view/'. $old_vid .'/'. $new_vid .'/0');
+  $links = array();
+  foreach (diff_avaiable_states('node') as $alternative_state => $label) {
+    if ($alternative_state == $state) {
+      // @todo: Should we show both or just alternatives?
+    }
+    $links[$alternative_state] = array(
+      'title' => $label,
+      'href' => "node/{$node->nid}/revisions/view/{$old_vid}/{$new_vid}" . ($alternative_state == $default_state ? '' : '/' . str_replace('_', '-', $alternative_state)),
+    );
   }
-  else {
-    $show_hide_markup_link = l(t('Hide Markup'), 'node/'. $node->nid .'/revisions/view/'. $old_vid .'/'. $new_vid .'/1');
+  if (count($links) > 1) {
+    $state_links = theme('links', array(
+      'links' => $links,
+      'attributes' => array('class' => array('links', 'inline')),
+    ));
+    $rows['states'] = array(
+      array(
+        'data' => $state_links,
+        'class' => 'diff-links',
+        'colspan' => 4,
+      ),
+    );
   }
-  $rows[] = array(
-    array(
-      'data' => $show_hide_markup_link,
-      'class' => 'diff-prevlink',
-      'colspan' => 4,
-    )
+  $rows = array_merge($rows, _diff_body_rows($old_node, $new_node, $state));
+
+  $build['diff_table'] = array(
+    '#theme' => 'table__diff__standard',
+    '#header' => $header,
+    '#rows' => $rows,
+    '#attributes' => array('class' => array('diff')),
+    '#colgroups' => _diff_default_cols(),
+    '#sticky' => FALSE,
   );
 
-  $rows = array_merge($rows, _diff_body_rows($old_node, $new_node, $remove_markup));
-  $output = theme('diff_table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('diff')), 'cols' => $cols));
-
-  if ($node->vid == $new_vid) {
-    $output .= '<div class="diff-section-title">' . t('Current revision:') . '</div>';
-  }
-  else {
-    $output .= '<div class="diff-section-title">' . t('Revision of !new_date:', array('!new_date' => format_date($new_node->revision_timestamp))) . '</div>';
+  // Allow users to hide or set the display mode of the preview.
+  if (node_is_page($node) && $view_mode = variable_get('diff_view_mode_preview_node_' . $new_node->type, 'full')) {
+    $header = '';
+    if ($node->vid == $new_vid) {
+      $header .= '<div class="diff-section-title">' . t('Current revision:') . '</div>';
+    }
+    else {
+      $header .= '<div class="diff-section-title">' . t('Revision of !new_date:', array('!new_date' => format_date($new_node->revision_timestamp))) . '</div>';
+    }
+    $build['diff_preview']['header']['#markup'] = $header;
+    // Don't include node links (final argument) when viewing the diff.
+    $build['diff_preview']['content'] = node_view($new_node, $view_mode);
+    if (isset($build['diff_preview']['content']['links'])) {
+      unset($build['diff_preview']['content']['links']);
+    }
   }
-  // Don't include node links (final argument) when viewing the diff.
-  $view = node_view($new_node, 'full');
-  $output .= drupal_render($view);
-  return $output;
+  return $build;
 }
 
 /**
- * Creates an array of rows which represent a diff between $old_node and $new_node.
- * The rows can be used via theme('diff_table') to be displayed.
+ * Creates an array of rows which represent the difference between nodes.
  *
- * @param $old_node
+ * @param object $old_node
  *   Node for comparison which will be displayed on the left side.
- * @param $new_node
+ * @param object $new_node
  *   Node for comparison which will be displayed on the right side.
- * @param $remove_markup
- *   Strip markup before doing the diff
+ * @param boolean $state
+ *   The state to render for the diff.
  */
-function _diff_body_rows($old_node, $new_node, $remove_markup = FALSE) {
-  drupal_add_css(drupal_get_path('module', 'diff') . '/diff.css');
+function _diff_body_rows($old_node, $new_node, $state = 'raw') {
+  // This is an unique index only, so no need for drupal_static().
+  static $table_row_counter = 0;
 
+  if ($theme = variable_get('diff_theme', 'default')) {
+    drupal_add_css(drupal_get_path('module', 'diff') . "/css/diff.{$theme}.css");
+  }
   module_load_include('inc', 'diff', 'includes/node');
 
   $rows = array();
   $any_visible_change = FALSE;
-  // @todo quick workaround for PHP >= 5.3.0 date_diff() conflict.
-  $node_diffs = _diff_module_invoke_all($old_node, $new_node, $remove_markup);
+  $context = array(
+    'entity_type' => 'node',
+    'states' => array($state),
+  );
 
-  // We start off assuming all form elements are in the correct order.
-  $node_diffs['#sorted'] = TRUE;
+  $node_diffs = diff_compare_entities($old_node, $new_node, $context);
 
-  // Recurse through all child elements.
-  $count = 0;
-  foreach (element_children($node_diffs) as $key) {
-    // Assign a decimal placeholder weight to preserve original array order.
-    if (!isset($node_diffs[$key]['#weight'])) {
-      $node_diffs[$key]['#weight'] = $count/1000;
+  // Track line numbers between multiple diffs.
+  $line_stats = array(
+    'counter' => array('x' => 0, 'y' => 0),
+    'offset' => array('x' => 0, 'y' => 0),
+  );
+
+  // Render diffs for each.
+  foreach ($node_diffs as $node_diff) {
+    $show_header = !empty($node_diff['#name']);
+    // These are field level settings.
+    if ($show_header && isset($node_diff['#settings']['show_header'])) {
+      $show_header = $show_header && $node_diff['#settings']['show_header'];
+    }
+
+    // Line counting and line header options.
+    if (empty($node_diff['#settings']['line_counter'])) {
+      $line_counter = FALSE;;
     }
     else {
-      // If one of the child elements has a weight then we will need to sort
-      // later.
-      unset($node_diffs['#sorted']);
+      $line_counter = $node_diff['#settings']['line_counter'];
+    }
+    // Every call to 'line' resets the counters.
+    if ($line_counter) {
+      $line_stats['counter']['x'] = 0;
+      $line_stats['counter']['y'] = 0;
+      if ($line_counter == 'line' && 0) {
+        $line_stats['offset']['x'] = 0;
+        $line_stats['offset']['y'] = 0;
+      }
+      $line_stats_ref = $line_stats;
+    }
+    else {
+      $line_stats_ref = NULL;
     }
-    $count++;
-  }
-
-  // One of the children has a #weight.
-  if (!isset($node_diffs['#sorted'])) {
-    uasort($node_diffs, "element_sort");
-  }
 
-  // Render diffs for each.
-  foreach ($node_diffs as $node_diff) {
-    $show_header = isset($node_diff['#format']['show_header']) ? $node_diff['#format']['show_header'] : FALSE;
-    if ($node_diff_rows = diff_get_rows($node_diff['#old'], $node_diff['#new'], $show_header)) {
-      $rows[] = array(array(
-        'data' => t('Changes to %name', array('%name' => $node_diff['#name'])),
-        'class' => 'diff-section-title',
-        'colspan' => 4
-      ));
-      $rows = array_merge($rows, $node_diff_rows);
+    list($old, $new) = diff_extract_state($node_diff, $state);
+    if ($node_diff_rows = diff_get_rows($old, $new, $line_counter && $line_counter != 'hidden', $line_stats_ref)) {
+      if ($line_counter && $line_counter != 'line') {
+        $line_stats['offset']['x'] += $line_stats_ref['counter']['x'];
+        $line_stats['offset']['y'] += $line_stats_ref['counter']['y'];
+      }
+      if ($show_header) {
+        $rows['diff-header-' . $table_row_counter++] = array(
+          array(
+            'data' => t('Changes to %name', array('%name' => $node_diff['#name'])),
+            'class' => 'diff-section-title',
+            'colspan' => 4,
+          ),
+        );
+      }
+      // To avoid passing counter to the Diff engine, index rows manually here
+      // to allow modules to interact with the table. i.e. no array_merge().
+      foreach ($node_diff_rows as $row) {
+        $rows['diff-row-' . $table_row_counter++] = $row;
+      }
       $any_visible_change = TRUE;
     }
   }
   if (!$any_visible_change) {
-    $rows[] = array(array(
-      'data' => t('No visible changes'),
-      'class' => 'diff-section-title',
-      'colspan' => 4
-    ));
-    // Needed to keep safari happy
-    $rows[] = array(
+    $rows['diff-empty-' . $table_row_counter++] = array(
+      array(
+        'data' => t('No visible changes'),
+        'class' => 'diff-section-title',
+        'colspan' => 4,
+      ),
+    );
+    // @todo: revise this.
+    // Needed to keep safari happy.
+    $rows['diff-empty-' . $table_row_counter++] = array(
       array('data' => ''),
       array('data' => ''),
       array('data' => ''),
@@ -333,41 +395,147 @@ function _diff_body_rows($old_node, $new_node, $remove_markup = FALSE) {
 }
 
 /**
- * Helper function to invoke hook_diff in all enabled modules that implement it.
- *
- * Don't use module_invoke_all() since if date.module is enabled will clash with
- * PHP 5.3's date_diff() function.
+ * Generic callback to compare two entities.
+ */
+function diff_compare_entities($left_entity, $right_entity, $context) {
+  $entity_type = $context['entity_type'];
+  list(, , $bundle) = entity_extract_ids($entity_type, $right_entity);
+  $context['bundle'] = $bundle;
+  $context['old_entity'] = $left_entity;
+  $context['new_entity'] = $right_entity;
+  $context += array(
+    'states' => array('raw'),
+  );
+  if (empty($context['view_mode']) && variable_get('diff_view_mode_standard_' . $entity_type . '_' . $bundle, FALSE)) {
+    $context['view_mode'] = 'diff_standard';
+  }
+  else {
+    $context['view_mode'] = FALSE;
+  }
+
+  $diff = module_invoke_all('entity_diff', $left_entity, $right_entity, $context);
+
+  // Allow other modules to interact directly with the results.
+  drupal_alter('entity_diff', $diff, $context);
+
+  // We start off assuming all form elements are in the correct order.
+  $diff['#sorted'] = TRUE;
+
+  // Field rows. Recurse through all child elements.
+  $count = 0;
+  foreach (element_children($diff) as $key) {
+    if (!isset($diff[$key]['#states'])) {
+      $diff[$key]['#states'] = array();
+    }
+
+    // Ensure that the element follows the new #states format.
+    if (isset($diff[$key]['#old'])) {
+      $diff[$key]['#states']['raw']['#old'] = $diff[$key]['#old'];
+      unset($diff[$key]['#old']);
+    }
+    if (isset($diff[$key]['#new'])) {
+      $diff[$key]['#states']['raw']['#new'] = $diff[$key]['#new'];
+      unset($diff[$key]['#new']);
+    }
+
+    // If requested, we can convert the .
+    foreach (array('raw', 'rendered') as $state) {
+      if (in_array($state . '_plain', $context['states'])) {
+        diff_markdown_state($diff[$key], $state);
+      }
+    }
+
+    // Assign a decimal placeholder weight to preserve original array order.
+    if (!isset($diff[$key]['#weight'])) {
+      $diff[$key]['#weight'] = $count / 1000;
+    }
+    else {
+      // If one child element has a weight then we will need to sort later.
+      unset($diff['#sorted']);
+    }
+    $count++;
+  }
+
+  // One of the children has a #weight.
+  if (!isset($diff['#sorted'])) {
+    uasort($diff, 'element_sort');
+  }
+
+  // Process the array and get line counts per field.
+  array_walk($diff, 'diff_process_state_lines');
+
+  return $diff;
+}
+
+function diff_process_state_lines(&$diff, $key) {
+  foreach ($diff['#states'] as $state => $data) {
+    if (isset($data['#old'])) {
+      if (is_string($data['#old'])) {
+        $diff['#states'][$state]['#old'] = explode("\n", $data['#old']);
+      }
+      $diff['#states'][$state]['#count_old'] = count($diff['#states'][$state]['#old']);
+    }
+    else {
+      $diff['#states'][$state]['#count_old'] = 0;
+    }
+    if (isset($data['#new'])) {
+      if (is_string($data['#new'])) {
+        $diff['#states'][$state]['#new'] = explode("\n", $data['#new']);
+      }
+      $diff['#states'][$state]['#count_new'] = count($diff['#states'][$state]['#new']);
+    }
+    else {
+      $diff['#states'][$state]['#count_new'] = 0;
+    }
+  }
+}
+
+/**
+ * Helper function to render plain states from the corresponding raw state.
  *
- * @todo figure out any else possible solution but not workaround.
- * @link http://drupal.org/node/639320
- * @see module_invoke_all()
+ * @param array $diff
+ *   The Diff Engine output array.
+ * @param string $state
+ *   The state to markdown.
  */
-function _diff_module_invoke_all($old_node, $new_node, $remove_markup = FALSE) {
-  $return = array();
-  foreach (module_implements('diff') as $module) {
-    if ($module == 'date') {
-      continue; // Avoid function name collision with date_diff().
+function diff_markdown_state(&$diff, $state) {
+  list($plain_old, $plain_new) = diff_extract_state($diff, $state . '_plain');
+  list($old, $new) = diff_extract_state($diff, $state);
+  $markdown = FALSE;
+  if (isset($diff['#settings']) && !empty($diff['#settings']['markdown'])) {
+    if (function_exists($diff['#settings']['markdown'])) {
+      $markdown = $diff['#settings']['markdown'];
+    }
+  }
+
+  if (!isset($plain_old) && isset($old)) {
+    if (is_array($old)) {
+      $diff['#states'][$state . '_plain']['#old'] = $markdown ? array_map($markdown, $old) : $old;
+    }
+    else {
+      $diff['#states'][$state . '_plain']['#old'] = $markdown ? $markdown($old) : $old;
     }
-    $function = "{$module}_diff";
-    $result = $function($old_node, $new_node, $remove_markup);
-    if (isset($result) && is_array($result)) {
-      $return = array_merge_recursive($return, $result);
+  }
+  if (!isset($plain_new) && isset($new)) {
+    if (is_array($new)) {
+      $diff['#states'][$state . '_plain']['#new'] = $markdown ? array_map($markdown, $new) : $new;
     }
-    elseif (isset($result)) {
-      $return[] = $result;
+    else {
+      $diff['#states'][$state . '_plain']['#new'] = $markdown ? $markdown($new) : $new;
     }
   }
-  return $return;
 }
 
 /**
  * Get the entry in the revisions list after $vid.
- * Returns FALSE if $vid is the last entry.
  *
- * @param $node_revisions
+ * @param array $node_revisions
  *   Array of node revision IDs in descending order.
- * @param $vid
+ * @param int $vid
  *   Version ID to look for.
+ *
+ * @return boolean|integer
+ *   Returns FALSE if $vid is the last entry.
  */
 function _diff_get_next_vid($node_revisions, $vid) {
   $previous = NULL;
@@ -382,12 +550,14 @@ function _diff_get_next_vid($node_revisions, $vid) {
 
 /**
  * Get the entry in the revision list before $vid.
- * Returns FALSE if $vid is the first entry.
  *
- * @param $node_revisions
+ * @param array $node_revisions
  *   Array of node revision IDs in descending order.
- * @param $vid
+ * @param integer $vid
  *   Version ID to look for.
+ *
+ * @return boolean|integer
+ *   Returns FALSE if $vid is the first entry.
  */
 function _diff_get_previous_vid($node_revisions, $vid) {
   $previous = NULL;
@@ -429,29 +599,30 @@ function _diff_default_header($old_header = '', $new_header = '') {
   return array(
     array(
       'data' => $old_header,
-      'colspan' => 2
+      'colspan' => 2,
     ),
     array(
       'data' => $new_header,
-      'colspan' => 2
-    )
+      'colspan' => 2,
+    ),
   );
 }
 
 /**
- * Show the inline diff for a given node, vid. If vid = 0 or no previous vid
- * exists for the given revision returns the normally rendered content of the
- * specified revision.
+ * Show the inline diff for a given node, vid.
+ *
+ * If vid = 0 or no previous vid exists for the given revision returns the
+ * normally rendered content of the specified revision.
  */
 function diff_inline_show($node, $vid = 0, $metadata = TRUE) {
   $new_node = $vid ? node_load($node->nid, $vid, TRUE) : clone $node;
-  node_build_content($new_node);
+  node_build_content($new_node, variable_get('diff_view_mode_inline_node_' . $new_node->type, 'diff_complete'));
   $new = drupal_render($new_node->content);
 
   $old = $vid ? _diff_get_previous_vid(node_revision_list($node), $vid) : 0;
   if ($old) {
     $old_node = node_load($node->nid, $old, TRUE);
-    node_build_content($old_node);
+    node_build_content($old_node, variable_get('diff_view_mode_inline_node_' . $old_node->type, 'diff_complete'));
     $old = drupal_render($old_node->content);
     $output = $metadata ? theme('diff_inline_metadata', array('node' => $new_node)) : '';
     $output .= diff_get_inline($old, $new);
diff --git a/diff.theme.inc b/diff.theme.inc
index 4b40170..8cd43c6 100644
--- a/diff.theme.inc
+++ b/diff.theme.inc
@@ -1,5 +1,4 @@
 <?php
-// $Id$
 
 /**
  * @file
@@ -7,8 +6,7 @@
  */
 
 /**
- * Theme function to display the revisions formular with means to select
- * two revisions.
+ * Theme function to display the revisions formular.
  */
 function theme_diff_node_revisions($vars) {
   $form = $vars['form'];
@@ -18,7 +16,7 @@ function theme_diff_node_revisions($vars) {
   $header = array(
     t('Revision'),
     array('data' => drupal_render($form['submit']), 'colspan' => 2),
-    array('data' => t('Operations'), 'colspan' => 2)
+    array('data' => t('Operations'), 'colspan' => 2),
   );
   if (isset($form['info']) && is_array($form['info'])) {
     foreach (element_children($form['info']) as $key) {
@@ -31,22 +29,44 @@ function theme_diff_node_revisions($vars) {
         $row[] = drupal_render($form['diff']['new'][$key]);
         $row[] = drupal_render($form['operations'][$key][0]);
         $row[] = drupal_render($form['operations'][$key][1]);
-        $rows[] = $row;
+        $rows[] = array(
+          'data' => $row,
+          'class' => array('diff-revision'),
+        );
       }
       else {
-        // its the current revision (no commands to revert or delete)
-        $row[] = array('data' => drupal_render($form['info'][$key]), 'class' => array('revision-current'));
-        $row[] = array('data' => drupal_render($form['diff']['old'][$key]), 'class' => array('revision-current'));
-        $row[] = array('data' => drupal_render($form['diff']['new'][$key]), 'class' => array('revision-current'));
-        $row[] = array('data' => t('current revision'), 'class' => array('revision-current'), 'colspan' => '2');
+        // The current revision (no commands to revert or delete).
+        $row[] = array(
+          'data' => drupal_render($form['info'][$key]),
+          'class' => array('revision-current'),
+        );
+        $row[] = array(
+          'data' => drupal_render($form['diff']['old'][$key]),
+          'class' => array('revision-current'),
+        );
+        $row[] = array(
+          'data' => drupal_render($form['diff']['new'][$key]),
+          'class' => array('revision-current'),
+        );
+        $row[] = array(
+          'data' => t('current revision'),
+          'class' => array('revision-current'),
+          'colspan' => '2',
+        );
         $rows[] = array(
           'data' => $row,
-          'class' => array('error'),
+          'class' => array('error diff-revision'),
         );
       }
     }
   }
-  $output .= theme('table', array('header' => $header, 'rows' => $rows));
+  $output .= theme('table__diff__revisions', array(
+    'header' => $header,
+    'rows' => $rows,
+    'sticky' => FALSE,
+    'attributes' => array('class' => 'diff-revisions'),
+  ));
+
   $output .= drupal_render_children($form);
   return $output;
 }
@@ -56,208 +76,6 @@ function theme_diff_node_revisions($vars) {
  */
 
 /**
- * Return a themed table. This is a modified version of theme_table, adding
- * colgroup tag and col tag options.
- *
- * @param $header
- *   An array containing the table headers. Each element of the array can be
- *   either a localized string or an associative array with the following keys:
- *   - "data": The localized title of the table column.
- *   - "field": The database field represented in the table column (required if
- *     user is to be able to sort on this column).
- *   - "sort": A default sort order for this column ("asc" or "desc").
- *   - Any HTML attributes, such as "colspan", to apply to the column header cell.
- * @param $rows
- *   An array of table rows. Every row is an array of cells, or an associative
- *   array with the following keys:
- *   - "data": an array of cells
- *   - Any HTML attributes, such as "class", to apply to the table row.
- *
- *   Each cell can be either a string or an associative array with the following keys:
- *   - "data": The string to display in the table cell.
- *   - "header": Indicates this cell is a header.
- *   - Any HTML attributes, such as "colspan", to apply to the table cell.
- *
- *   Here's an example for $rows:
- *   @verbatim
- *   $rows = array(
- *     // Simple row
- *     array(
- *       'Cell 1', 'Cell 2', 'Cell 3'
- *     ),
- *     // Row with attributes on the row and some of its cells.
- *     array(
- *       'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => 'funky'
- *     )
- *   );
- *   @endverbatim
- *
- * @param $attributes
- *   An array of HTML attributes to apply to the table tag.
- * @param $caption
- *   A localized string to use for the <caption> tag.
- * @param $cols
- *   An array of table colum groups. Every column group is an array of columns,
- *   or an associative array with the following keys:
- *   - "data": an array of cells
- *   - Any HTML attributes, such as "class", to apply to the table column group.
- *
- *   Each column can be either an empty array or associative array with the following keys:
- *   - Any HTML attributes, such as "class", to apply to the table column group.
- *
- *   Here's an example for $cols:
- *   @verbatim
- *   $cols = array(
- *     // Simple colgroup.
- *     array(),
- *     // Simple colgroup with attributes.
- *     array(
- *       'data'  => array(), 'colspan' => 2, 'style' => 'color: green;',
- *     ),
- *     // Simple colgroup with one col.
- *     array(
- *       array(),
- *     ),
- *     // Colgroup with attributes on the colgroup and some of its cols.
- *     array(
- *       'data'  => array(array('class' => 'diff-marker'), array('colspan' => 2)), 'class' => 'funky',
- *     ),
- *   );
- *   @endverbatim
- *
- *   The HTML will look as follows:
- *   @verbatim
- *   <table>
- *     <!-- Simple colgroup. -->
- *     <colgroup />
- *
- *     <!-- Simple colgroup with attributes. -->
- *     <colgroup colspan="2" style="color: green;" />
- *
- *     <!-- Simple colgroup with one col. -->
- *     <colgroup>
- *       <col />
- *     </colgroup>
- *
- *     <!-- Colgroup with attributes on the colgroup and some of its cols. -->
- *     <colgroup class="funky">
- *       <col class="diff-marker" />
- *       <col colspan="2" />
- *     </colgroup>
- *     ...
- *   </table>
- *   @endverbatim
- *
- * @return
- *   An HTML string representing the table.
- */
-function theme_diff_table($vars) {
-  $header     = isset($vars['header']) ? $vars['header'] : array();
-  $rows       = isset($vars['rows']) ? $vars['rows'] : array();
-  $attributes = isset($vars['attributes']) ? $vars['attributes'] : array();
-  $caption    = isset($vars['caption']) ? $vars['caption'] : '';
-  $cols       = isset($vars['cols']) ? $vars['cols'] : array();
-
-  $output = '<table' . drupal_attributes($attributes) . ">\n";
-
-  if (isset($caption)) {
-    $output .= '<caption>' . $caption . "</caption>\n";
-  }
-
-  // Format the table columns:
-  if (count($cols)) {
-    foreach ($cols as $number => $col) {
-      $attributes = array();
-
-      // Check if we're dealing with a simple or complex column
-      if (isset($col['data'])) {
-        foreach ($col as $key => $value) {
-          if ($key == 'data') {
-            $cells = $value;
-          }
-          else {
-            $attributes[$key] = $value;
-          }
-        }
-      }
-      else {
-        $cells = $col;
-      }
-
-      // Build colgroup
-      if (is_array($cells) && count($cells)) {
-        $output .= ' <colgroup' . drupal_attributes($attributes) . '>';
-        $i = 0;
-        foreach ($cells as $cell) {
-          $output .= ' <col' . drupal_attributes($cell) . ' />';
-        }
-        $output .= " </colgroup>\n";
-      }
-      else {
-        $output .= ' <colgroup' . drupal_attributes($attributes) . " />\n";
-      }
-    }
-  }
-
-  // Format the table header:
-  if (count($header)) {
-    $ts = tablesort_init($header);
-    $output .= ' <thead><tr>';
-    foreach ($header as $cell) {
-      $cell = tablesort_header($cell, $header, $ts);
-      $output .= _theme_table_cell($cell, TRUE);
-    }
-    $output .= " </tr></thead>\n";
-  }
-
-  // Format the table rows:
-  $output .= "<tbody>\n";
-  if (count($rows)) {
-    $flip = array('even' => 'odd', 'odd' => 'even');
-    $class = 'even';
-    foreach ($rows as $number => $row) {
-      $attributes = array();
-
-      // Check if we're dealing with a simple or complex row
-      if (isset($row['data'])) {
-        foreach ($row as $key => $value) {
-          if ($key == 'data') {
-            $cells = $value;
-          }
-          else {
-            $attributes[$key] = $value;
-          }
-        }
-      }
-      else {
-        $cells = $row;
-      }
-
-      // Add odd/even class
-      $class = $flip[$class];
-      if (isset($attributes['class'])) {
-        $attributes['class'] .= ' ' . $class;
-      }
-      else {
-        $attributes['class'] = $class;
-      }
-
-      // Build row
-      $output .= ' <tr' . drupal_attributes($attributes) . '>';
-      $i = 0;
-      foreach ($cells as $cell) {
-        $cell = tablesort_cell($cell, $header, $ts, $i++);
-        $output .= _theme_table_cell($cell);
-      }
-      $output .= " </tr>\n";
-    }
-  }
-
-  $output .= "</tbody></table>\n";
-  return $output;
-}
-
-/**
  * Theme function for a header line in the diff.
  */
 function theme_diff_header_line($vars) {
@@ -282,7 +100,9 @@ function theme_diff_empty_line($vars) {
  * Theme function for inline diff form.
  */
 function theme_diff_inline_form($vars) {
-  drupal_add_css(drupal_get_path('module', 'diff') . '/diff.css');
+  if ($theme = variable_get('diff_theme', 'default')) {
+    drupal_add_css(drupal_get_path('module', 'diff') . "/css/diff.{$theme}.css");
+  }
   return drupal_render_children($vars['form']);
 }
 
@@ -290,7 +110,9 @@ function theme_diff_inline_form($vars) {
  * Display inline diff metadata.
  */
 function theme_diff_inline_metadata($vars) {
-  drupal_add_css(drupal_get_path('module', 'diff') . '/diff.css');
+  if ($theme = variable_get('diff_theme', 'default')) {
+    drupal_add_css(drupal_get_path('module', 'diff') . "/css/diff.{$theme}.css");
+  }
   $node = $vars['node'];
 
   $output = "<div class='diff-inline-metadata clear-block'>";
diff --git a/diff.tokens.inc b/diff.tokens.inc
new file mode 100644
index 0000000..9a8a7d2
--- /dev/null
+++ b/diff.tokens.inc
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Builds placeholder replacement tokens for diff-related data.
+ */
+
+/**
+ * Implements hook_token_info().
+ */
+function diff_token_info() {
+  $node['diff'] = array(
+    'name' => t('Latest diff'),
+    'description' => t('The difference between the current revision and the previous revision of this node.'),
+  );
+  $node['diff-markdown'] = array(
+    'name' => t('Latest diff (markdowned)'),
+    'description' => t('The difference between the current revision and the previous revision of this node, but comparison uses a markdowned state.'),
+  );
+
+  return array(
+    'tokens' => array('node' => $node),
+  );
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function diff_tokens($type, $tokens, array $data = array(), array $options = array()) {
+  $sanitize = !empty($options['sanitize']);
+
+  $replacements = array();
+
+  if ($type == 'node' && !empty($data['node'])) {
+    $node = $data['node'];
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        // Basic diff standard comparison information.
+        case 'diff':
+        case 'diff-markdown':
+          $revisons = node_revision_list($node);
+          if (count($revisons) == 1) {
+            $replacements[$original] = t('(No previous revision available.)');
+          }
+          else {
+            module_load_include('inc', 'diff', 'diff.pages');
+            $old_vid = _diff_get_previous_vid($revisons, $node->vid);
+            $state = $name == 'diff' ? 'raw' : 'raw_plain';
+            $build = diff_diffs_show($node, $old_vid, $node->vid, $state);
+            unset($build['diff_table']['#rows']['states']);
+            unset($build['diff_table']['#rows']['navigation']);
+            unset($build['diff_preview']);
+
+            $output = drupal_render_children($build);
+            $replacements[$original] = $sanitize ? check_plain($output) : $output;
+          }
+          break;
+
+      }
+    }
+  }
+  return $replacements;
+}
diff --git a/includes/file.inc b/includes/file.inc
new file mode 100644
index 0000000..678ff46
--- /dev/null
+++ b/includes/file.inc
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Provide diff field functions for the file module.
+ */
+
+/**
+ * Diff field callback for preloading file entities.
+ */
+function file_field_diff_view_prepare(&$old_items, &$new_items, $context) {
+  $fids = array();
+  foreach (array_merge_recursive($old_items, $new_items) as $info) {
+    $fids[$info['fid']] = $info['fid'];
+  }
+  $files = file_load_multiple($fids);
+
+  foreach ($old_items as $delta => $info) {
+    $old_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
+  }
+  foreach ($new_items as $delta => $info) {
+    $new_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
+  }
+}
+
+/**
+ * Diff field callback for parsing file field comparative values.
+ */
+function file_field_diff_view($items, $context) {
+  $field = $context['field'];
+  $instance = $context['instance'];
+  $settings = $context['settings'];
+
+  $diff_items = array();
+  foreach ($items as $delta => $item) {
+    if (isset($item['file'])) {
+      $output = array();
+
+      // We populate as much as possible to allow the best flexability in any
+      // string overrides.
+      $t_args = array();
+      foreach ($item as $key => $value) {
+        if (is_scalar($value)) {
+          $t_args['!' . $key] = $value;
+        }
+      }
+      // Some states do not have the file properties in the item, so put these
+      // out of the main file object.
+      if (!empty($item['file'])) {
+        $file_values = (array) $item['file'];
+        foreach ($file_values as $key => $value) {
+          if (is_scalar($value) && !isset($t_args['!' . $key])) {
+            $t_args['!' . $key] = $value;
+          }
+        }
+      }
+
+      $output['file'] = t('File: !filename', $t_args);
+      if ($settings['compare_description_field'] && !empty($instance['settings']['description_field'])) {
+        if (!empty($item['description'])) {
+          $output['description'] = t('Description: !description', $t_args);
+        }
+      }
+      if ($settings['show_id']) {
+        $output['fid'] = t('File ID: !fid', $t_args);
+      }
+      if ($settings['compare_display_field'] && !empty($field['settings']['display_field'])) {
+        $output['display'] = $item['display'] ? t('Displayed') : t('Hidden');
+      }
+      $diff_items[$delta] = implode('; ', $output);
+    }
+  }
+
+  return $diff_items;
+}
+
+/**
+ * Provide default field comparison options.
+ */
+function file_field_diff_default_options($field_type) {
+  return array(
+    'show_id' => 0,
+    'compare_display_field' => 0,
+    'compare_description_field' => 0,
+  );
+}
+
+/**
+ * Provide a form for setting the field comparison options.
+ */
+function file_field_diff_options_form($field_type, $settings) {
+  $options_form = array();
+  $options_form['show_id'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Show file ID'),
+    '#default_value' => $settings['show_id'],
+  );
+  $options_form['compare_description_field'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Compare description field'),
+    '#default_value' => $settings['compare_description_field'],
+    '#description' => t('This is only used if the "Enable <em>Description</em> field" is checked in the instance settings.'),
+  );
+  $options_form['compare_display_field'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Compare display state field'),
+    '#default_value' => $settings['compare_display_field'],
+    '#description' => t('This is only used if the "Enable <em>Display</em> field" is checked in the field settings.'),
+  );
+  return $options_form;
+}
diff --git a/includes/image.inc b/includes/image.inc
new file mode 100644
index 0000000..cb93616
--- /dev/null
+++ b/includes/image.inc
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * Provide diff field functions for the image module.
+ */
+
+/**
+ * Diff field callback for preloading the image file entities.
+ */
+function image_field_diff_view_prepare(&$old_items, &$new_items, $context) {
+  $fids = array();
+  foreach (array_merge_recursive($old_items, $new_items) as $info) {
+    $fids[$info['fid']] = $info['fid'];
+  }
+  $files = file_load_multiple($fids);
+
+  foreach ($old_items as $delta => $info) {
+    $old_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
+  }
+  foreach ($new_items as $delta => $info) {
+    $new_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
+  }
+}
+
+/**
+ * Diff field callback for parsing image field comparative values.
+ */
+function image_field_diff_view($items, $context) {
+  $instance = $context['instance'];
+  $settings = $context['settings'];
+
+  $diff_items = array();
+  foreach ($items as $delta => $item) {
+    if (isset($item['file'])) {
+      $output = array();
+
+      // We populate as much as possible to allow the best flexability in any
+      // string overrides.
+      $t_args = array();
+      foreach ($item as $key => $value) {
+        if (is_scalar($value)) {
+          $t_args['!' . $key] = $value;
+        }
+      }
+      // Some states do not have the file properties in the item, so put these
+      // out of the main file object.
+      if (!empty($item['file'])) {
+        $file_values = (array) $item['file'];
+        foreach ($file_values as $key => $value) {
+          if (is_scalar($value) && !isset($t_args['!' . $key])) {
+            $t_args['!' . $key] = $value;
+          }
+        }
+      }
+
+      $output[] = t('Image: !filename', $t_args);
+      if ($settings['compare_alt_field'] && !empty($instance['settings']['alt_field'])) {
+        if (!empty($item['alt'])) {
+          $output[] = t('Alt: !alt', $t_args);
+        }
+      }
+      if ($settings['compare_title_field'] && !empty($instance['settings']['title_field'])) {
+        if (!empty($item['title'])) {
+          $output[] = t('Title: !title', $t_args);
+        }
+      }
+      if ($settings['show_id']) {
+        $output[] = t('File ID: !fid', $t_args);
+      }
+      $diff_items[$delta] = implode('; ', $output);
+    }
+  }
+
+  return $diff_items;
+}
+
+/**
+ * Provide default field comparison options.
+ */
+function image_field_diff_default_options($field_type) {
+  return array(
+    'show_id' => 0,
+    'compare_alt_field' => 0,
+    'compare_title_field' => 0,
+  );
+}
+
+/**
+ * Provide a form for setting the field comparison options.
+ */
+function image_field_diff_options_form($field_type, $settings) {
+  $options_form = array();
+  $options_form['show_id'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Show image ID'),
+    '#default_value' => $settings['show_id'],
+  );
+  $options_form['compare_alt_field'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Compare <em>Alt</em> field'),
+    '#default_value' => $settings['compare_alt_field'],
+    '#description' => t('This is only used if the "Enable <em>Alt</em> field" is checked in the instance settings.'),
+  );
+  $options_form['compare_title_field'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Compare <em>Title</em> field'),
+    '#default_value' => $settings['compare_title_field'],
+    '#description' => t('This is only used if the "Enable <em>Title</em> field" is checked in the instance settings.'),
+  );
+  return $options_form;
+}
diff --git a/includes/list.inc b/includes/list.inc
new file mode 100644
index 0000000..d1f62e3
--- /dev/null
+++ b/includes/list.inc
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Provide diff field functions for the List module.
+ */
+
+/**
+ * Diff field callback for parsing list field comparative values.
+ */
+function list_field_diff_view($items, $context) {
+  $field = $context['field'];
+  $instance = $context['instance'];
+  $settings = $context['settings'];
+
+  $diff_items = array();
+  $allowed_values = list_allowed_values($field, $instance, $context['entity_type'], $context['entity']);
+  foreach ($items as $delta => $item) {
+    // Fairly complex condition to prevent duplicate "key (key)" type rendering.
+    if (isset($allowed_values[$item['value']]) &&
+        $allowed_values[$item['value']] != $item['value'] &&
+        strlen($allowed_values[$item['value']])) {
+      switch ($settings['compare']) {
+        case 'both':
+          $diff_items[$delta] = $allowed_values[$item['value']] . ' (' . $item['value'] . ')';
+          break;
+
+        case 'label':
+          $diff_items[$delta] = $allowed_values[$item['value']];
+          break;
+
+        default:
+          $diff_items[$delta] = $item['value'];
+          break;
+
+      }
+    }
+    else {
+      // If no match was found for the label, fall back to the key.
+      $diff_items[$delta] = $item['value'];
+    }
+  }
+  return $diff_items;
+}
+
+/**
+ * Provide default field comparison options.
+ */
+function list_field_diff_default_options($field_type) {
+  return array(
+    'compare' => 'label',
+  );
+}
+
+/**
+ * Provide a form for setting the field comparison options.
+ */
+function list_field_diff_options_form($field_type, $settings) {
+  $options_form = array();
+  $options_form['compare'] = array(
+    '#type' => 'radios',
+    '#title' => t('Comparison method'),
+    '#options' => array(
+      'label' => t('Label'),
+      'key' => t('Key'),
+      'both' => t('Label (key)'),
+    ),
+    '#default_value' => $settings['compare'],
+  );
+  return $options_form;
+}
diff --git a/includes/node.inc b/includes/node.inc
index 39dc326..3f0bca3 100644
--- a/includes/node.inc
+++ b/includes/node.inc
@@ -1,77 +1,105 @@
 <?php
-// $Id$
 
 /**
  * @file
- * Implements hook_diff() for node.module (body and title).
+ * Provide diff functions for the node module.
  */
 
 /**
- * Implements hook_diff() for node.module (body and title).
+ * Implements hook_entity_diff().
+ *
+ * This function compares core node properties. This is currently limited to:
+ *   - title: The title of the node.
+ *
+ * @param object $old_node
+ *   The older node revision.
+ * @param object $new_node
+ *   The newer node revision.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - old_entity: The older entity.
+ *   - new_entity: The newer entity.
+ *   - view_mode: The view mode to use. Defaults to FALSE. If no view mode is
+ *                given, the recommended fallback view mode is 'default'.
+ *   - states: An array of view states. These could be one of:
+ *     - raw: The raw value of the diff, the classic 7.x-2.x view.
+ *     - rendered: The rendered HTML as determined by the view mode. Only
+ *                 return markup for this state if the value is normally shown
+ *                 by this view mode. The user will most likely be able to see
+ *                 the raw or raw_plain state, so this is optional.
+ *
+ *                 The rendering state is a work in progress.
+ *
+ *     Conditionally, you can get these states, but setting these will override
+ *     the user selectable markdown method.
+ *
+ *     - raw_plain: As raw, but text should be markdowned.
+ *     - rendered_plain: As rendered, but text should be markdowned.
+ *
+ * @return array
+ *   An associative array of values keyed by the entity property.
+ *
+ *   This is effectively an unnested Form API-like structure.
+ *
+ *   States are returned as follows:
+ *
+ *   $results['line'] = array(
+ *     '#name' => t('Line'),
+ *     '#states' => array(
+ *       'raw' => array(
+ *         '#old' => '<p class="line">This was the old line number [tag].</p>',
+ *         '#new' => '<p class="line">This is the new line [tag].</p>',
+ *       ),
+ *       'rendered' => array(
+ *         '#old' => '<p class="line">This was the old line number <span class="line-number">57</span>.</p>',
+ *         '#new' => '<p class="line">This is the new line <span class="line-number">57</span>.</p>',
+ *       ),
+ *     ),
+ *   );
+ *
+ *   For backwards compatability, no changes are required to support states,
+ *   but it is recommended to provide a better UI for end users.
+ *
+ *   For example, the following example is equivalent to returning the raw
+ *   state from the example above.
+ *
+ *   $results['line'] = array(
+ *     '#name' => t('Line'),
+ *     '#old' => '<p class="line">This was the old line number [tag].</p>',
+ *     '#new' => '<p class="line">This is the new line [tag].</p>',
+ *   );
  */
-function node_diff($old_node, $new_node, $remove_markup) {
-
+function node_entity_diff($old_node, $new_node, $context) {
   $result = array();
-  $type = node_type_get_type($new_node);
-  $result['title'] = array(
-    '#name' => $type->title_label,
-    '#old' => array($old_node->title),
-    '#new' => array($new_node->title),
-    '#weight' => -5,
-    '#format' => array(
-      'show_header' => FALSE,
-    )
-  );
-
-  // @TODO: abstract this to work with all field types and/or split this
-  // integration out to be more generic.
-  $instances = field_info_instances('node', field_extract_bundle('node', $type));
-  foreach ($instances as $instance) {
-    $field_name = $instance['field_name'];
-    $langcode = field_language('node', $new_node, $field_name);
-    if (isset($new_node->{$field_name}[$langcode]) && !empty($new_node->{$field_name}[$langcode])) {
-      foreach (array_keys($new_node->{$field_name}[$langcode]) as $delta) {
-        if (isset($new_node->{$field_name}[$langcode][$delta]['value']) && isset($old_node->{$field_name}[$langcode][$delta]['value'])) {
-          $view_old = $old_node->{$field_name}[$langcode][$delta]['value'];
-          $view_new = $new_node->{$field_name}[$langcode][$delta]['value'];
-          $result["{$field_name}_{$delta}"] = array(
-            '#name' => $instance['label'],
-            '#old' => explode("\n", ($remove_markup) ? drupal_html_to_text($view_old) : $view_old),
-            '#new' => explode("\n", ($remove_markup) ? drupal_html_to_text($view_new) : $view_new),
-          );
-        }
-        elseif (isset($new_node->{$field_name}[$langcode][$delta]['value'])) {
-          // We have a newly input value where there was none in the previous
-          // version of the node.
-          $view_new = $new_node->{$field_name}[$langcode][$delta]['value'];
-          $result["{$field_name}_{$delta}"] = array(
-            '#name' => $instance['label'],
-            '#old' => '',
-            '#new' => explode("\n", ($remove_markup) ? drupal_html_to_text($view_new) : $view_new),
-          );
-        }
-        elseif (isset($old_node->{$field_name}[$langcode][$delta]['value']) && !empty($old_node->{$field_name}[$langcode][$delta]['value'])) {
-          // We have a value that has been removed from the field.
-          $view_old = $old_node->{$field_name}[$langcode][$delta]['value'];
-          $result["{$field_name}_{$delta}"] = array(
-            '#name' => $instance['label'],
-            '#old' => explode("\n", ($remove_markup) ? drupal_html_to_text($view_old) : $view_old),
-            '#new' => '',
+  if ($context['entity_type'] == 'node') {
+    $type = node_type_get_type($new_node);
+    $result['title'] = array(
+      '#name' => $type->title_label,
+      '#states' => array(),
+      '#weight' => -5,
+      '#settings' => array(
+        // Global setting - 'diff_show_header_' . $entity_type
+        'show_header' => variable_get('diff_show_header_node', 1),
+      ),
+    );
+    foreach ($context['states'] as $state) {
+      switch ($state) {
+        case 'rendered':
+          $result['title']['#states'][$state] = array(
+            '#old' => l($old_node->title, 'node/' . $old_node->title),
+            '#new' => l($new_node->title, 'node/' . $new_node->title),
           );
-        }
-      }
-    }
-    elseif (isset($old_node->{$field_name}[$langcode])) {
-      // We have a value that has been removed from the field.
-      foreach (array_keys($old_node->{$field_name}[$langcode]) as $delta) {
-        if (isset($old_node->{$field_name}[$langcode][$delta]['value'])) {
-          $view_old = $old_node->{$field_name}[$langcode][$delta]['value'];
-          $result["{$field_name}_{$delta}"] = array(
-            '#name' => $instance['label'],
-            '#old' => explode("\n", ($remove_markup) ? drupal_html_to_text($view_old) : $view_old),
-            '#new' => '',
+          break;
+
+        // We specify a default so that the title is allows compared.
+        case 'raw':
+        default:
+          $result['title']['#states'][$state] = array(
+            '#old' => array($old_node->title),
+            '#new' => array($new_node->title),
           );
-        }
+          break;
       }
     }
   }
diff --git a/includes/taxonomy.inc b/includes/taxonomy.inc
new file mode 100644
index 0000000..5765c9c
--- /dev/null
+++ b/includes/taxonomy.inc
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Implements pusdeo-hook hook_field_diff_view() for the Taxonomy module.
+ */
+
+/**
+ * Diff field callback for preloading term entities.
+ */
+function taxonomy_field_diff_view_prepare(&$old_items, &$new_items, $context) {
+  $tids = array();
+  foreach (array_merge_recursive($old_items, $new_items) as $info) {
+    $tids[$info['tid']] = $info['tid'];
+  }
+  $terms = taxonomy_term_load_multiple($tids);
+  foreach ($old_items as $delta => $info) {
+    $old_items[$delta]['term'] = isset($terms[$info['tid']]) ? $terms[$info['tid']] : NULL;
+  }
+  foreach ($new_items as $delta => $info) {
+    $new_items[$delta]['term'] = isset($terms[$info['tid']]) ? $terms[$info['tid']] : NULL;
+  }
+}
+
+/**
+ * Diff field callback for parsing term field comparative values.
+ */
+function taxonomy_field_diff_view($items, $context) {
+  $settings = $context['settings'];
+  $instance = $context['instance'];
+
+  $diff_items = array();
+  foreach ($items as $delta => $item) {
+    if (!empty($item['tid'])) {
+      if ($item['tid'] == 'autocreate') {
+        $diff_items[$delta] = t('!term_name (new)', array('!term_name' => $item['name']));
+      }
+      elseif (empty($item['term'])) {
+        $diff_items[$delta] = t('Missing term reference (!tid)', array('!tid' => $item['tid']));
+      }
+      else {
+        $output = array();
+        $output['name'] = $item['term']->name;
+        if ($settings['show_id']) {
+          $output['tid'] = t('Term ID: !tid', array('!tid' => $item['term']->tid));
+        }
+        $diff_items[$delta] = implode('; ', $output);
+      }
+    }
+  }
+  if (!empty($settings['sort']) && !empty($diff_items)) {
+    if ($settings['sort'] == DIFF_SORT_VALUE || $instance['widget']['type'] == 'taxonomy_autocomplete') {
+      usort($diff_items, 'uasort_taxonomy_field_diff_terms');
+    }
+  }
+  return $diff_items;
+}
+
+/**
+ * Helper function for sorting terms.
+ */
+function uasort_taxonomy_field_diff_terms($a, $b) {
+  // We need to use t() to test for string overrides.
+  $missing_text = t('Missing term reference');
+  $a_missing = strpos($a, $missing_text) === 0;
+  $b_missing = strpos($b, $missing_text) === 0;
+  if ($a_missing && $b_missing) {
+    return strnatcmp($a, $b);
+  }
+  elseif ($a_missing xor $b_missing) {
+    return $a_missing ? 100 : -100;
+  }
+  return strnatcmp($a, $b);
+}
+
+/**
+ * Provide default field comparison options.
+ */
+function taxonomy_field_diff_default_options($field_type) {
+  return array(
+    'show_id' => 0,
+    'sort' => DIFF_SORT_CUSTOM,
+  );
+}
+
+/**
+ * Provide a form for setting the field comparison options.
+ */
+function taxonomy_field_diff_options_form($field_type, $settings) {
+  $options_form = array();
+  $options_form['show_id'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Show term ID'),
+    '#default_value' => $settings['show_id'],
+  );
+  $options_form['sort'] = array(
+    '#type' => 'radios',
+    '#title' => t('Sort'),
+    '#options' => array(
+      DIFF_SORT_NONE => t('No sort'),
+      DIFF_SORT_VALUE => t('Sort'),
+      DIFF_SORT_CUSTOM => t('Sort if free tagging field'),
+    ),
+    '#default_value' => $settings['sort'],
+  );
+  return $options_form;
+}
diff --git a/includes/text.inc b/includes/text.inc
new file mode 100644
index 0000000..8d15a16
--- /dev/null
+++ b/includes/text.inc
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * @file
+ * Provide diff field functions for the Text module.
+ */
+
+/**
+ * Diff field callback for parsing text field comparative values.
+ */
+function text_field_diff_view($items, $context) {
+  $field = $context['field'];
+  $instance = $context['instance'];
+  $settings = $context['settings'];
+
+  $diff_items = array();
+  foreach ($items as $delta => $item) {
+    $diff_items[$delta] = array();
+
+    // Compute the format for appending to the label.
+    $format_text = '';
+    if ($instance['settings']['text_processing'] && $settings['compare_format']) {
+      $format_id = empty($item['format']) ? filter_fallback_format() : $item['format'];
+      if ($format = filter_format_load($format_id)) {
+        $format_text = $format->name;
+      }
+      else {
+        $format_text = t('Missing format !format', array('!format' => $format_id));
+      }
+    }
+
+    // Compare the summary fields.
+    $summary = $field['type'] == 'text_with_summary' && $settings['compare_summary'];
+    if ($summary) {
+      // Allow users to optionally clean system specific characters.
+      if (empty($item['summary'])) {
+        $diff_items[$delta][] = t('Summary field is empty.');
+      }
+      else {
+        if ($format_text) {
+          $diff_items[$delta][] = t('Summary (!text_format):', array('!text_format' => $format_text));
+        }
+        else {
+          $diff_items[$delta][] = t('Summary:');
+        }
+        $diff_items[$delta][] = diff_normalise_text($item['summary']);
+      }
+    }
+
+    // Only show label if field has summary displayed.
+    if ($summary) {
+      if ($format_text) {
+        $diff_items[$delta][] = t('Content (!text_format):', array('!text_format' => $format_text));
+      }
+      else {
+        $diff_items[$delta][] = t('Content:');
+      }
+    }
+
+    // Allow users to optionally clean system specific characters.
+    $diff_items[$delta][] = diff_normalise_text($item['value']);
+
+    // If no summary, append the format selection to the bottom of the screen.
+    // This prevents adding the "Content (format)" label.
+    if ($format_text && !$summary) {
+      $diff_items[$delta][] = t('Text format: !text_format', array('!text_format' => $format_text));
+    }
+
+    $diff_items[$delta] = $diff_items[$delta];
+  }
+  return $diff_items;
+}
+
+/**
+ * Provide default field comparison options.
+ */
+function text_field_diff_default_options($field_type) {
+  // Overrides the global 'markdown' setting which does not escape HTML.
+  $settings = array(
+    'compare_format' => 0,
+    'markdown' => 'drupal_html_to_text',
+    'line_counter' => 'line',
+  );
+  if ($field_type == 'text_with_summary') {
+    $settings += array(
+      'compare_summary' => 0,
+    );
+  }
+
+  return $settings;
+}
+
+/**
+ * Provide a form for setting the field comparison options.
+ */
+function text_field_diff_options_form($field_type, $settings) {
+  $options_form = array();
+  $options_form['compare_format'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Compare format'),
+    '#default_value' => $settings['compare_format'],
+    '#description' => t('This is only used if the "Text processing" instance settings are set to <em>Filtered text (user selects text format)</em>.'),
+  );
+  if ($field_type == 'text_with_summary') {
+    $options_form['compare_summary'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Compare summary separately'),
+      '#default_value' => $settings['compare_summary'],
+      '#description' => t('This is only used if the "Summary input" option is checked in the instance settings.'),
+    );
+  }
+  return $options_form;
+}
diff --git a/js/diff.js b/js/diff.js
new file mode 100644
index 0000000..2c873e9
--- /dev/null
+++ b/js/diff.js
@@ -0,0 +1,58 @@
+(function ($) {
+
+Drupal.behaviors.diffRevisions = {
+  attach: function (context, settings) {
+    var $rows = $('table.diff-revisions tbody tr');
+    function updateDiffRadios() {
+      var newTd = false;
+      var oldTd = false;
+      if (!$rows.length) {
+        return true;
+      }
+      $rows.removeClass('selected').each(function() {
+        var $row = $(this);
+        $row.removeClass('selected');
+        var $inputs = $row.find('input[type="radio"]');
+        var $oldRadio = $inputs.filter('[name="old"]').eq(0);
+        var $newRadio = $inputs.filter('[name="new"]').eq(0);
+        if (!$oldRadio.length || !$newRadio.length) {
+          return true;
+        }
+        if ($oldRadio.attr('checked')) {
+          oldTd = true;
+          $row.addClass('selected');
+          $oldRadio.css('visibility', 'visible');
+          $newRadio.css('visibility', 'hidden');
+        } else if ($newRadio.attr('checked')) {
+          newTd = true;
+          $row.addClass('selected');
+          $oldRadio.css('visibility', 'hidden');
+          $newRadio.css('visibility', 'visible');
+        } else {
+          if (Drupal.settings.diffRevisionRadios == 'linear') {
+            if (newTd && oldTd) {
+              $oldRadio.css('visibility', 'visible');
+              $newRadio.css('visibility', 'hidden');
+            } else if (newTd) {
+              $newRadio.css('visibility', 'visible');
+              $oldRadio.css('visibility', 'visible');
+            } else {
+              $newRadio.css('visibility', 'visible');
+              $oldRadio.css('visibility', 'hidden');
+            }
+          } else {
+            $newRadio.css('visibility', 'visible');
+            $oldRadio.css('visibility', 'visible');
+          }
+        }
+      });
+      return true;
+    }
+    if (Drupal.settings.diffRevisionRadios) {
+      $rows.find('input[name="new"], input[name="old"]').click(updateDiffRadios);
+      updateDiffRadios();
+    }
+  }
+};
+
+})(jQuery);
diff --git a/readme.txt b/readme.txt
index 5294d4b..f4a20dc 100644
--- a/readme.txt
+++ b/readme.txt
@@ -34,8 +34,7 @@ changes to HTML entities, etc.
 
 API
 ---
-This module offers `hook_diff()` which modules may use to inject their changes
-into the presentation of the diff. For example, this is used by `node.inc`.
+See diff.api.php
 
 Maintainers
 -----------
