diff --git a/project_issue.module b/project_issue.module
index bb1f7b4..fed7c21 100644
--- a/project_issue.module
+++ b/project_issue.module
@@ -1703,8 +1703,11 @@ function project_issue_nodereference_autocomplete_validate($element, &$form_stat
 /**
  * Implements hook_field_formatter_info().
  *
- * Provides a formatter for nodereference fields that uses
- * theme_project_issue_issue_link().
+ * Provides:
+ * i)  two formatters for nodereference fields, which use
+ *     theme_project_issue_issue_link().
+ * ii) a formatter for file fields which renders a table of files with
+ *     associated metadata.
  */
 function project_issue_field_formatter_info() {
   return array(
@@ -1718,16 +1721,133 @@ function project_issue_field_formatter_info() {
       'field types' => array('entityreference'),
       'multiple values' => FIELD_BEHAVIOR_DEFAULT,
     ),
+    'issue_files_summary_table' => array(
+      'label' => t('List of files and metadata styled as a table'),
+      'field types' => array('file'),
+      'settings' => array(
+        // File type options:  all, patches, images, text, or custom
+        'filetypes' => array('all'),
+        // Customtypes: string with comma-separated list of file extensions
+        'customtypes' => '',
+        // Show Hidden: checkbox determining whether to include 'hidden' files
+        'showhidden' => FALSE,
+        // Sort By: default column to use for sorting
+        'sortby' => 'fid',
+        // Sort Order:  ASC or DESC
+        'sortorder' => 'DESC',
+        // Group by: none, extension - render individual table by group
+        'groupby' => 'extension',
+        // Allow Hooks: allow other modules to alter data before rendering
+        'allowhooks' => TRUE,
+      ),
+    ),
   );
 }
 
 /**
+ * Implements hook_field_formatter_settings_form().
+ *
+ * Contains the settings form for the issue_files_summary_table formatter.
+ */
+function project_issue_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+  if ($display['type'] == 'issue_files_summary_table') {
+    $display = $instance['display'][$view_mode];
+    $settings = $display['settings'];
+    $widget_settings = $field['settings'];
+
+    // File types checkbox
+    $options = array('all', 'patches', 'images', 'text', 'custom');
+    $form['filetypes'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('File types to display:'),
+      '#options' => $options,
+      '#default_value' => $settings['filetypes'],
+    );
+    // Custom file types textfield
+    $form['customtypes'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Extension list:'),
+      '#description' => t('Comma separated list of file extensions to include (only valid with "Custom" above):'),
+      '#default_value' => $settings['customtypes'],
+    );
+    $form['showhidden'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Show files which have been marked as "hidden"'),
+      '#default_value' => $settings['showhidden'],
+    );
+    $form['sortby'] = array(
+      '#type' => 'select',
+      '#title' => t('Sort by:'),
+      '#options' => array('fid' => 'File ID', 'uri' => 'File Name', 'type' => 'File Extension'),
+      '#default_value' => $settings['sortby'],
+    );
+    $form['sortorder'] = array(
+      '#type' => 'radios',
+      '#title' => t('Sort order:'),
+      '#options' => array(t('ASC'), t('DESC')),
+      '#default_value' => $settings['sortorder'],
+    );
+    $form['groupby'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Group into seperate tables by type.'),
+      '#default_value' => $settings['groupby'],
+    );
+    $form['allowhooks'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Allow other modules to alter data before rendering.'),
+      '#default_value' => $settings['allowhooks'],
+    );
+  }
+  return $form;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ *
+ * Contains the settings summary for the issue_files_summary_table formatter.
+ */
+function project_issue_field_formatter_settings_summary($field, $instance, $view_mode) {
+  $summary = '';
+  if ($display['type'] == 'issue_files_summary_table') {
+    $display = $instance['display'][$view_mode];
+    $settings = $display['settings'];
+
+    if ($settings['filetypes']['all'] == 'all') {
+      $summary = t('Listing all file types.');
+    }
+    else {
+      $filetypes = implode(', ', array_filter($settings['filetypes']));
+      $summary = t('Listing the following file types: @types', array('@types' => $filetypes));
+    }
+  }
+
+  return $summary;
+}
+
+/**
  * Implements hook_field_formatter_view().
  */
 function project_issue_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
-  $elements = array();
 
+  switch ($display['type']) {
+    case 'issue_id':
+    case 'issue_id_assigned':
+      $elements = project_issue_field_formatter_view_issue($entity_type, $entity, $field, $instance, $langcode, $items, $display);
+      break;
+    case 'issue_files_summary_table':
+      $elements = project_issue_field_formatter_view_files($entity_type, $entity, $field, $instance, $langcode, $items, $display);
+      break;
+  }
+  return $elements;
+}
+
+/**
+ * Renders the issue_id formatters for project_issue_field_formatter_view().
+ */
+function project_issue_field_formatter_view_issue($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+  $elements = array();
   $settings = $display['settings'];
+
   $include_assigned = $display['type'] == 'issue_id_assigned' ? TRUE : FALSE;
 
   $target_type = $field['settings']['target_type'];
@@ -1784,11 +1904,110 @@ function project_issue_field_formatter_view($entity_type, $entity, $field, $inst
       '#markup' => $output,
     );
   }
+  return $elements;
+}
+
+
+
+
+/**
+ * Renders the file table formatter for project_issue_field_formatter_view().
+ */
+function project_issue_field_formatter_view_files($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+  $elements = array();
+  $settings = $display['settings'];
+
+  // Available fields:  fid, display, description, uid, filename, uri, filemime, filesize, status, timestamp
+  // Needed: Comment ID, Filename, Author ... and possibly some sort of 'hidden' flag
+
+  // fid -> nid from file_usage;
+  //
+
+  // All comments -> comment_get_thread($node, $mode, $comment_per_page)
+  // All files -> field_data_field_issue_changes
+  //           -> entity_type = comment
+  //           -> bundle = comment_node_project_issue
+  //           -> deleted = 0
+  //           -> entity_id = cid
+  //           -> revision_id = cid???
+  //           -> field_issue_changes_field_name = field_issue_files
+  //           -> Get 'field_issue_changes_old_value' and 'field_issue_changes_new_value'
+
+  drupal_set_message('Entity: <pre>' . print_r($entity, TRUE));
+  drupal_set_message('Field: <pre>' . print_r($field, TRUE));
+  drupal_set_message('Instance: <pre>' . print_r($instance, TRUE));
+  drupal_set_message('Items: <pre>' . print_r($items, TRUE));
+  drupal_set_message('Display: <pre>' . print_r($display, TRUE));
+
+
 
   return $elements;
 }
 
 /**
+ * Adds a 'comment id' value to each field_issue_files entry on a node.
+ */
+function project_issue_add_file_cids(&$node) {
+
+  $language = $node->language;
+  $files = $node->field_issue_files[$language];
+
+  // Obtain list of cids for this node
+  $query = db_select('comment', 'c');
+  $query->addField('c', 'cid');
+  $query->condition('c.nid', $node->nid)
+        ->addTag('node_access')
+        ->addTag('comment_filter')
+        ->addMetaData('node', $node)
+        ->orderBy('c.cid', 'ASC');
+
+  $cids = $query->execute()->fetchCol();
+
+  // Load each comment which contains a change to the field_issue_files field.
+  $query = new EntityFieldQuery();
+  $query->entityCondition('entity_type', 'comment')
+    ->entityCondition('bundle', 'comment_node_project_issue')
+    ->entityCondition('entity_id', $cids, 'IN')
+    ->propertyCondition('nid', $node->nid)
+    ->propertyCondition('status', 1)
+    ->fieldCondition('field_issue_changes', 'field_name', 'field_issue_files', '=');
+  $result = $query->execute();
+
+  $cids = array();
+  $comments = array();
+  if (isset($result['comment'])) {
+    $cids = array_keys($result['comment']);
+    $comments = entity_load('comment', $cids);
+  }
+
+  $fids = array();
+  foreach ($comments as $comment) {
+    $language = !empty($comment->language) ? $comment->language : 'und';
+    // Iterate over the changes, isolating those related to field_issue_files
+    foreach ($comment->field_issue_changes[$language] as $change) {
+      if ($change['field_name'] == 'field_issue_files') {
+        // Create array of 'fid' => 'cid' for all file values after this change
+        $added = array();
+        foreach($change['new_value'] as $file) {
+          $added[$file['fid']] = $comment->cid;
+        }
+        // Remove file entries for values which existed before this comment
+        foreach($change['old_value'] as $file) {
+          unset($added[$file['fid']]);
+        }
+      }
+    }
+  }
+  // Update the $node->field_issue_files field, adding a 'cid' key to each file
+  // If no comment id is found for a given file, we set it to '0'.
+  foreach($files as $key => $value) {
+    $node->field_issue_files[$language][$key]['cid'] = isset($added[$value['fid']]) ? $added[$value['fid']] : 0;
+  }
+}
+
+
+
+/**
  * Find the flag name for following issues for this issue type (if any).
  *
  * If the flag tracker module (http://drupal.org/project/flag_tracker) is enabled,
