From b6afed7f8abe904bf030df0adc019f9e556a8f61 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 18 Aug 2016 17:42:50 -0400
Subject: [PATCH 11/25] FIXME: introduce class edit_context

---
 gcc/Makefile.in          |    1 +
 gcc/diagnostic-color.c   |    4 +
 gcc/edit-context.c       | 1256 ++++++++++++++++++++++++++++++++++++++++++++++
 gcc/edit-context.h       |   55 ++
 gcc/selftest-run-tests.c |    1 +
 gcc/selftest.h           |    1 +
 6 files changed, 1318 insertions(+)
 create mode 100644 gcc/edit-context.c
 create mode 100644 gcc/edit-context.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 7a0160f..5148c3f 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1560,6 +1560,7 @@ OBJS = \
 # Objects in libcommon.a, potentially used by all host binaries and with
 # no target dependencies.
 OBJS-libcommon = diagnostic.o diagnostic-color.o diagnostic-show-locus.o \
+	edit-context.o \
 	pretty-print.o intl.o \
 	vec.o input.o version.o hash-table.o ggc-none.o memory-block.o \
 	selftest.o
diff --git a/gcc/diagnostic-color.c b/gcc/diagnostic-color.c
index f76c87b..13d4221 100644
--- a/gcc/diagnostic-color.c
+++ b/gcc/diagnostic-color.c
@@ -168,6 +168,9 @@ static struct color_cap color_dict[] =
   { "range2", SGR_SEQ (COLOR_FG_BLUE), 6, false },
   { "locus", SGR_SEQ (COLOR_BOLD), 5, false },
   { "quote", SGR_SEQ (COLOR_BOLD), 5, false },
+  { "diff-hunk", SGR_SEQ (COLOR_FG_CYAN), 9, false },
+  { "diff-delete", SGR_SEQ (COLOR_FG_RED), 11, false },
+  { "diff-insert", SGR_SEQ (COLOR_FG_GREEN), 11, false },
   { NULL, NULL, 0, false }
 };
 
@@ -197,6 +200,7 @@ colorize_stop (bool show_color)
 
 /* Parse GCC_COLORS.  The default would look like:
    GCC_COLORS='error=01;31:warning=01;35:note=01;36:range1=32:range2=34;locus=01:quote=01'
+   FIXME
    No character escaping is needed or supported.  */
 static bool
 parse_gcc_colors (void)
diff --git a/gcc/edit-context.c b/gcc/edit-context.c
new file mode 100644
index 0000000..9971212
--- /dev/null
+++ b/gcc/edit-context.c
@@ -0,0 +1,1256 @@
+/* Applying fix-its on behalf of the user to their code.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "line-map.h"
+#include "edit-context.h"
+#include "pretty-print.h"
+#include "diagnostic-color.h"
+#include "selftest.h"
+
+/* This file implements a way to apply fix-its on behalf of the user,
+   via a class edit_context; the other classes are support classes for
+   edit_context.
+
+   A complication here is that fix-its are expressed relative to coordinates
+   in the file when it was parsed, before any changes have been made, and
+   so if there's more that one fix-it to be applied, we have to adjust
+   later fix-its to allow for the changes made by earlier ones.  This
+   is done by the various "get_effective_column" methods.
+
+   The "filename" params are required to outlive the edit_context (no
+   copy of the underlying str is taken, just the ptr).  */
+
+/* Forward decls.  class edit_context is declared within edit-context.h.
+   The other types are declared here.  */
+class edit_context;
+class edited_file;
+class line_state;
+class line_event;
+  class insert_event;
+  class replace_event;
+
+/* The state of one named file within an edit_context: the filename,
+   the current content of the file after applying all changes so far, and
+   a record of the changes, so that further changes can be applied in
+   the correct place.  */
+
+class edited_file
+{
+ public:
+  edited_file (const char *filename);
+  ~edited_file ();
+  static void delete_cb (edited_file *file);
+
+  const char *get_filename () const { return m_filename; }
+  const char *get_content () const { return m_content; }
+
+  bool apply_insert (int line, int column, const char *str, int len);
+  bool apply_replace (int line, int start_column,
+		      int finish_column,
+		      const char *replacement_str,
+		      int replacement_len);
+  int get_effective_column (int line, int column);
+
+  static int call_print_diff (const char *, edited_file *file,
+			      void *pp)
+  {
+    file->print_diff ((pretty_printer *)pp);
+    return 0;
+  }
+  static int call_apply_changes (const char *, edited_file *file,
+				 void *)
+  {
+    file->apply_changes ();
+    return 0;
+  }
+
+ private:
+  void print_diff (pretty_printer *pp);
+  void print_line_in_diff (pretty_printer *pp, int line_num);
+  void apply_changes ();
+  line_state *get_line (int line);
+  line_state &get_or_insert_line (int line);
+  void ensure_capacity (size_t len);
+  void ensure_terminated ();
+  char *get_line_start (int line_num);
+  void ensure_line_start_index ();
+  void evict_line_start_index ();
+  int get_num_lines ();
+
+  const char *m_filename;
+  char *m_content;
+  size_t m_len;
+  size_t m_alloc_sz;
+  typed_splay_tree<int, line_state *> m_edited_lines;
+  auto_vec<int> m_line_start_index;
+};
+
+/* A mapping from pre-edit columns to post-edit columns
+   within one line of one file.  */
+
+class line_state
+{
+ public:
+  line_state (int line_num) : m_line_num (line_num), m_line_events () {}
+  ~line_state ();
+  static void delete_cb (line_state *ls);
+
+  int get_line_num () const { return m_line_num; }
+
+  int get_effective_column (int orig_column) const;
+  void apply_insert (int column, int len);
+  void apply_replace (int start, int finish, int len);
+
+ private:
+  int m_line_num;
+  auto_vec <line_event *> m_line_events;
+};
+
+/* Abstract base class for representing events that have occurred
+   on one line of one file.  */
+
+class line_event
+{
+ public:
+  virtual ~line_event () {}
+  virtual int get_effective_column (int orig_column) const = 0;
+};
+
+/* Concrete subclass of line_event: an insertion of some text
+   at some column on the line.
+
+   Subsequent events will need their columns adjusting if they're
+   are on this line and their column is >= the insertion point.  */
+
+class insert_event : public line_event
+{
+ public:
+  insert_event (int column, int len) : m_column (column), m_len (len) {}
+  int get_effective_column (int orig_column) const FINAL OVERRIDE
+  {
+    if (orig_column >= m_column)
+      return orig_column + m_len;
+    else
+      return orig_column;
+  }
+
+ private:
+  int m_column;
+  int m_len;
+};
+
+/* Concrete subclass of line_event: the replacement of some text
+   betweeen some columns on the line.
+
+   Subsequent events will need their columns adjusting if they're
+   are on this line and their column is >= the finish point.  */
+
+class replace_event : public line_event
+{
+ public:
+  replace_event (int start, int finish, int len) : m_start (start),
+    m_finish (finish), m_delta (len - (finish + 1 - start)) {}
+
+  int get_effective_column (int orig_column) const FINAL OVERRIDE
+  {
+    if (orig_column >= m_start)
+      return orig_column += m_delta;
+    else
+      return orig_column;
+  }
+
+ private:
+  int m_start;
+  int m_finish;
+  int m_delta;
+};
+
+/* Implementation of class edit_context.  */
+
+/* edit_context's ctor.  */
+
+edit_context::edit_context ()
+: m_valid (true),
+  m_files (strcmp, NULL, edited_file::delete_cb)
+{}
+
+/* Add any fixits within RICHLOC to this context, recording the
+   changes that they make.  */
+
+void
+edit_context::add_fixits (rich_location *richloc)
+{
+  for (unsigned i = 0; i < richloc->get_num_fixit_hints (); i++)
+    {
+      const fixit_hint *hint = richloc->get_fixit_hint (i);
+      switch (hint->get_kind ())
+	{
+	case fixit_hint::INSERT:
+	  if (!apply_insert ((const fixit_insert *)hint))
+	    /* Failure.  */
+	    m_valid = false;
+	  break;
+	case fixit_hint::REPLACE:
+	  if (!apply_replace ((const fixit_replace *)hint))
+	    /* Failure.  */
+	    m_valid = false;
+	  break;
+	default:
+	  gcc_unreachable ();
+	}
+    }
+}
+
+/* Get the content of the given file, with fix-its applied.  */
+
+const char *
+edit_context::get_content (const char *filename)
+{
+  edited_file &file = get_or_insert_file (filename);
+  return file.get_content ();
+}
+
+/* Map a location before the edits to a column number after the edits.
+   This method is for the selftests.  */
+
+int
+edit_context::get_effective_column (const char *filename, int line,
+				    int column)
+{
+  edited_file *file = get_file (filename);
+  if (!file)
+    return column;
+  return file->get_effective_column (line, column);
+}
+
+/* Generate a unified diff.  The resulting string should be freed by the
+   caller.  Primarily for selftests.  */
+
+char *
+edit_context::generate_diff ()
+{
+  pretty_printer pp;
+  print_diff (&pp);
+  return xstrdup (pp_formatted_text (&pp));
+}
+
+/* Print a unified diff to PP, showing the changes made within the
+   context.  */
+
+void
+edit_context::print_diff (pretty_printer *pp)
+{
+  m_files.foreach (edited_file::call_print_diff, pp);
+}
+
+/* If the context is valid, write back all changes to the underlying
+   files in the filesystem and return true.
+
+   Otherwise return false.  */
+
+bool
+edit_context::apply_changes ()
+{
+  if (!m_valid)
+    return false;
+  m_files.foreach (edited_file::call_apply_changes, NULL);
+  return true;
+}
+
+/* Attempt to apply the given fixit.  Return true if it can be
+   applied, or false otherwise.  */
+
+bool
+edit_context::apply_insert (const fixit_insert *insert)
+{
+  expanded_location exploc = expand_location (insert->get_location ());
+
+  if (exploc.column == 0)
+    return false;
+
+  edited_file &file = get_or_insert_file (exploc.file);
+  return file.apply_insert (exploc.line, exploc.column, insert->get_string (),
+			    insert->get_length ());
+}
+
+/* Attempt to apply the given fixit.  Return true if it can be
+   applied, or false otherwise.  */
+
+bool
+edit_context::apply_replace (const fixit_replace *replace)
+{
+  source_range range = replace->get_range ();
+
+  expanded_location start = expand_location (range.m_start);
+  expanded_location finish = expand_location (range.m_finish);
+  if (start.file != finish.file)
+    return false;
+  if (start.line != finish.line)
+    return false;
+  if (start.column == 0)
+    return false;
+  if (finish.column == 0)
+    return false;
+
+  edited_file &file = get_or_insert_file (start.file);
+  return file.apply_replace (start.line, start.column, finish.column,
+			     replace->get_string (),
+			     replace->get_length ());
+}
+
+/* Locate the edited_file * for FILENAME, if any
+   Return NULL if there isn't one.  */
+
+edited_file *
+edit_context::get_file (const char *filename)
+{
+  gcc_assert (filename);
+  return m_files.lookup (filename);
+}
+
+/* Locate the edited_file for FILENAME, adding one if there isn't one.  */
+
+edited_file &
+edit_context::get_or_insert_file (const char *filename)
+{
+  gcc_assert (filename);
+
+  edited_file *file = get_file (filename);
+  if (file)
+    return *file;
+
+  /* Not found.  */
+  file = new edited_file (filename);
+  m_files.insert (filename, file);
+  return *file;
+}
+
+/* Implementation of class edited_file.  */
+
+/* Callback for m_edited_lines, for comparing line numbers.  */
+
+static int line_comparator (int a, int b)
+{
+  return a - b;
+}
+
+/* edited_file's constructor.  Read the contents of the file from
+   the filesystem into memory.  */
+
+edited_file::edited_file (const char *filename)
+: m_filename (filename),
+  m_content (NULL), m_len (0), m_alloc_sz (0),
+  m_edited_lines (line_comparator, NULL, line_state::delete_cb)
+{
+  // TODO: load the file...
+
+  FILE *f_in = fopen (filename, "r");
+  if (!f_in)
+    ; // FIXME
+
+  /* Read content, allocating FIXME.  */
+  char buf[4096];
+  size_t iter_sz_in;
+
+  while ( (iter_sz_in = fread (buf, 1, sizeof (buf), f_in)) )
+    {
+      gcc_assert (m_alloc_sz >= m_len);
+      size_t old_len = m_len;
+      m_len += iter_sz_in;
+      ensure_capacity (m_len);
+      memcpy (m_content + old_len, buf, iter_sz_in);
+    }
+
+  if (!feof (f_in))
+    // FIXME:
+    ; /* ::selftest::fail_formatted (loc, "error reading from %s: %s", path,
+	 xstrerror (errno)); */
+
+  fclose (f_in);
+
+  ensure_terminated ();
+}
+
+/* edited_file's dtor.  */
+
+edited_file::~edited_file ()
+{
+  free (m_content);
+}
+
+/* A callback for deleting edited_file *, for use as a
+   delete_value_fn for edit_context::m_files.  */
+
+void
+edited_file::delete_cb (edited_file *file)
+{
+  delete file;
+}
+
+/* Insert the string INSERT_STR with length INSERT_LEN at LINE and COLUMN,
+   updating the in-memory copy of the file, and the record of edits to
+   the line.  */
+
+bool
+edited_file::apply_insert (int line, int column,
+			   const char *insert_str,
+			   int insert_len)
+{
+  column = get_effective_column (line, column);
+
+  int start_offset = column - 1;
+  gcc_assert (start_offset >= 0);
+
+  /* Ensure buffer is big enough.  */
+  size_t new_len = m_len + insert_len;
+  ensure_capacity (new_len);
+
+  char *line_start = get_line_start (line);
+  if (!line_start)
+    return false;
+
+  char *suffix = line_start + start_offset;
+  size_t len_suffix = (m_content + m_len) - suffix;
+
+  /* Move successor content into position.  They overlap, so use memmove.  */
+  memmove (line_start + start_offset + insert_len,
+	   suffix, len_suffix);
+
+  /* Replace target content.  They don't overlap, so use memcpy.  */
+  memcpy (line_start + start_offset,
+	  insert_str,
+	  insert_len);
+
+  m_len = new_len;
+
+  ensure_terminated ();
+  evict_line_start_index ();
+
+  /* Record the insertion, so that followup changes to this line
+     can have their columns adjusted as appropriate.  */
+  line_state &ls = get_or_insert_line (line);
+  ls.apply_insert (column, insert_len);
+
+  return true;
+}
+
+/* Replace columns START_COLUMN through FINISH_COLUMN of LINE
+   with the string REPLACEMENT_STR of length REPLACEMENT_LEN,
+   updating the in-memory copy of the file, and the record of edits to
+   the line.  */
+
+bool
+edited_file::apply_replace (int line, int start_column,
+			    int finish_column,
+			    const char *replacement_str,
+			    int replacement_len)
+{
+  start_column = get_effective_column (line, start_column);
+  finish_column = get_effective_column (line, finish_column);
+
+  int start_offset = start_column - 1;
+  int end_offset = finish_column - 1;
+
+  gcc_assert (start_offset >= 0);
+  gcc_assert (end_offset >= 0);
+
+  gcc_assert (start_column <= finish_column);
+
+  size_t victim_len = end_offset - start_offset + 1;
+
+  /* Ensure buffer is big enough.  */
+  size_t new_len = m_len + replacement_len - victim_len;
+  ensure_capacity (new_len);
+
+  char *line_start = get_line_start (line);
+  if (!line_start)
+    return false;
+
+  char *suffix = line_start + end_offset + 1;
+  size_t len_suffix = (m_content + m_len) - suffix;
+
+  /* Move successor content into position.  They overlap, so use memmove.  */
+  memmove (line_start + start_offset + replacement_len,
+	   suffix, len_suffix);
+
+  /* Replace target content.  They don't overlap, so use memcpy.  */
+  memcpy (line_start + start_offset,
+	  replacement_str,
+	  replacement_len);
+
+  m_len = new_len;
+
+  ensure_terminated ();
+  evict_line_start_index ();
+
+  /* Record the replacement, so that followup changes to this line
+     can have their columns adjusted as appropriate.  */
+  line_state &ls = get_or_insert_line (line);
+  ls.apply_replace (start_column, finish_column, replacement_len);
+
+  return true;
+}
+
+/* Given line LINE, map from COLUMN in the input file to its current
+   column after edits have been applied.  */
+
+int
+edited_file::get_effective_column (int line, int column)
+{
+  const line_state *ls = get_line (line);
+  if (!ls)
+    return column;
+  return ls->get_effective_column (column);
+}
+
+/* Print a unified diff to PP, showing any changes that have occurred
+   to this file.  */
+
+void
+edited_file::print_diff (pretty_printer *pp)
+{
+  line_state *ls = m_edited_lines.min ();
+
+  const int context_lines = 3;
+
+  while (ls)
+    {
+      int start_of_hunk = ls->get_line_num ();
+      start_of_hunk -= context_lines;
+      if (start_of_hunk < 1)
+	start_of_hunk = 1;
+
+      /* Locate end of hunk, merging in changed lines
+	 that are sufficiently close.  */
+      while (true)
+	{
+	  line_state *next_ls
+	    = m_edited_lines.successor (ls->get_line_num ());
+	  if (!next_ls)
+	    break;
+	  if (ls->get_line_num () + context_lines
+	      >= next_ls->get_line_num () - context_lines)
+	    ls = next_ls;
+	  else
+	    break;
+	}
+      int end_of_hunk = ls->get_line_num ();
+      end_of_hunk += context_lines;
+      if (end_of_hunk > get_num_lines ())
+	end_of_hunk = get_num_lines ();
+
+      int num_lines = end_of_hunk - start_of_hunk + 1;
+
+      pp_string (pp, colorize_start (pp_show_color (pp), "diff-hunk"));
+      pp_printf (pp, "@@ -%i,%i +%i,%i @@\n", start_of_hunk, num_lines,
+		 start_of_hunk, num_lines);
+      pp_string (pp, colorize_stop (pp_show_color (pp)));
+
+      int line_num = start_of_hunk;
+      while (line_num <= end_of_hunk)
+	{
+	  line_state *ls = get_line (line_num);
+	  if (ls)
+	    {
+	      /* We have an edited line.
+		 Consolidate into runs of changed lines.  */
+	      const int first_changed_line_in_run = line_num;
+	      while (get_line (line_num))
+		line_num++;
+	      const int last_changed_line_in_run = line_num - 1;
+
+	      pp_string (pp, colorize_start (pp_show_color (pp),
+					     "diff-delete"));
+
+	      /* Show old version of lines.  */
+	      for (line_num = first_changed_line_in_run;
+		   line_num <= last_changed_line_in_run;
+		   line_num++)
+		{
+		  int line_size;
+		  const char *old_line
+		    = location_get_source_line (m_filename, line_num,
+						&line_size);
+		  pp_character (pp, '-');
+		  for (int i = 0; i < line_size; i++)
+		    pp_character (pp, old_line[i]);
+		  pp_character (pp, '\n');
+		}
+
+	      pp_string (pp, colorize_stop (pp_show_color (pp)));
+
+	      pp_string (pp, colorize_start (pp_show_color (pp),
+					     "diff-insert"));
+
+	      /* Show new version of lines.  */
+	      for (line_num = first_changed_line_in_run;
+		   line_num <= last_changed_line_in_run;
+		   line_num++)
+		{
+		  pp_character (pp, '+');
+		  print_line_in_diff (pp, line_num);
+		}
+
+	      pp_string (pp, colorize_stop (pp_show_color (pp)));
+
+	    }
+	  else
+	    {
+	      /* Unchanged line.  */
+	      pp_character (pp, ' ');
+	      print_line_in_diff (pp, line_num);
+	      line_num++;
+	    }
+	}
+
+      ls = m_edited_lines.successor (ls->get_line_num ());
+    }
+}
+
+/* FIXME.  */
+
+void
+edited_file::print_line_in_diff (pretty_printer *pp, int line_num)
+{
+  const char *line = get_line_start (line_num);
+  while (char ch = *line++)
+    {
+      pp_character (pp, ch);
+      if (ch == '\n')
+	break;
+    }
+}
+
+/* Write the state from memory back to the filesystem.  */
+
+void
+edited_file::apply_changes ()
+{
+  FILE *f = fopen (get_filename (), "w");
+  if (!f)
+    {
+      // FIXME
+      return;
+    }
+
+  size_t n = fwrite (m_content, 1, m_len, f);
+  // FIXME: error handling?
+
+  fclose (f);
+}
+
+/* FIXME.  */
+
+line_state *
+edited_file::get_line (int line)
+{
+  return m_edited_lines.lookup (line);
+}
+
+/* FIXME.  */
+
+line_state &
+edited_file::get_or_insert_line (int line)
+{
+  line_state *ls = get_line (line);
+  if (ls)
+    return *ls;
+  ls = new line_state (line);
+  m_edited_lines.insert (line, ls);
+  return *ls;
+}
+
+/* FIXME.  */
+
+void
+edited_file::ensure_capacity (size_t len)
+{
+  /* Allow 1 extra byte for 0-termination.  */
+  if (m_alloc_sz < (len + 1))
+    {
+      size_t new_alloc_sz = (len + 1) * 2;
+      m_content = (char *)xrealloc (m_content, new_alloc_sz);
+      m_alloc_sz = new_alloc_sz;
+    }
+}
+
+/* FIXME.  */
+
+void
+edited_file::ensure_terminated ()
+{
+  /* 0-terminate the buffer.  */
+  gcc_assert (m_len < m_alloc_sz);
+  m_content[m_len] = '\0';
+}
+
+/* FIXME.  */
+char *
+edited_file::get_line_start (int line_num)
+{
+  ensure_line_start_index ();
+  return &m_content[m_line_start_index[line_num - 1]];
+}
+
+/* FIXME.  */
+
+void
+edited_file::ensure_line_start_index ()
+{
+  if (m_line_start_index.length () == 0)
+    {
+      int offset = 0;
+      m_line_start_index.safe_push (0);
+      while (char ch = m_content[offset++])
+	{
+	  if (ch == '\n')
+	    if (m_content[offset])
+	      m_line_start_index.safe_push (offset);
+	  if (ch == '\0')
+	    break;
+	}
+    }
+}
+
+/* FIXME.  */
+
+void
+edited_file::evict_line_start_index ()
+{
+  m_line_start_index.truncate (0);
+}
+
+/* FIXME.  */
+
+int
+edited_file::get_num_lines ()
+{
+  ensure_line_start_index ();
+  return m_line_start_index.length ();
+}
+
+/* Implementation of class line_state.  */
+
+/* FIXME.  */
+
+void
+line_state::delete_cb (line_state *ls)
+{
+  delete ls;
+}
+
+/* FIXME.  */
+
+line_state::~line_state ()
+{
+  int i;
+  line_event *event;
+  FOR_EACH_VEC_ELT (m_line_events, i, event)
+    delete event;
+}
+
+/* Map a location before the edits to a column number after the edits,
+   within a specific line.  */
+
+int
+line_state::get_effective_column (int orig_column) const
+{
+  int i;
+  line_event *event;
+  FOR_EACH_VEC_ELT (m_line_events, i, event)
+    orig_column = event->get_effective_column (orig_column);
+  return orig_column;
+}
+
+/* TODO:
+   record the column manipulations
+   apply the column manipulations when applying fixits.  */
+
+/* FIXME.  */
+
+void
+line_state::apply_insert (int column, int len)
+{
+  m_line_events.safe_push (new insert_event (column, len));
+}
+
+/* FIXME.  */
+
+void
+line_state::apply_replace (int start, int finish, int len)
+{
+  m_line_events.safe_push (new replace_event (start, finish, len));
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Selftests of code-editing.  */
+
+/* FIXME.  */
+
+static void
+test_applying_fixits_insert (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     .....................0000000001111111.
+     .....................1234567890123456.  */
+  const char *content = ("/* before */\n"
+			 "foo = bar.field;\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt (case_);
+  linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
+
+  /* Add a comment in front of "bar.field".  */
+  location_t start = linemap_position_for_column (line_table, 7);
+  rich_location richloc (line_table, start);
+  richloc.add_fixit_insert (start, "/* inserted */");
+
+  if (start > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  edit_context edit;
+  edit.add_fixits (&richloc);
+  const char *result = edit.get_content (filename);
+  if (start <= LINE_MAP_MAX_LOCATION_WITH_COLS)
+    ASSERT_STREQ ("/* before */\n"
+		  "foo = /* inserted */bar.field;\n"
+		  "/* after */\n", result);
+
+  /* Verify that locations on other lines aren't affected by the change.  */
+  ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
+  ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
+
+  /* Verify locations on the line before the change.  */
+  ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
+  ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
+
+  /* Verify locations on the line at and after the change.  */
+  ASSERT_EQ (21, edit.get_effective_column (filename, 2, 7));
+  ASSERT_EQ (22, edit.get_effective_column (filename, 2, 8));
+
+  /* Verify diff.  */
+  char *diff = edit.generate_diff (); // 3, false);
+  ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
+		" /* before */\n"
+		"-foo = bar.field;\n"
+		"+foo = /* inserted */bar.field;\n"
+		" /* after */\n", diff);
+  free (diff);
+}
+
+/* FIXME.  */
+
+static void
+test_applying_fixits_growing_replace (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     .....................0000000001111111.
+     .....................1234567890123456.  */
+  const char *content = ("/* before */\n"
+			 "foo = bar.field;\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt (case_);
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  /* Replace "field" with "m_field".  */
+  location_t start = linemap_position_for_column (line_table, 11);
+  location_t finish = linemap_position_for_column (line_table, 15);
+  location_t field = make_location (start, start, finish);
+  rich_location richloc (line_table, field);
+  source_range range;
+  range.m_start = start;
+  range.m_finish = finish;
+  richloc.add_fixit_replace (range, "m_field");
+
+  edit_context edit;
+  edit.add_fixits (&richloc);
+  const char *result = edit.get_content (filename);
+  if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
+    {
+      ASSERT_STREQ ("/* before */\n"
+		    "foo = bar.m_field;\n"
+		    "/* after */\n", result);
+
+      // TODO: verify effective locations
+
+      /* Verify diff.  */
+      char *diff = edit.generate_diff ();
+      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
+		    " /* before */\n"
+		    "-foo = bar.field;\n"
+		    "+foo = bar.m_field;\n"
+		    " /* after */\n", diff);
+      free (diff);
+    }
+}
+
+/* FIXME.  */
+
+static void
+test_applying_fixits_shrinking_replace (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     .....................000000000111111111.
+     .....................123456789012345678.  */
+  const char *content = ("/* before */\n"
+			 "foo = bar.m_field;\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt (case_);
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  /* Replace "field" with "m_field".  */
+  location_t start = linemap_position_for_column (line_table, 11);
+  location_t finish = linemap_position_for_column (line_table, 17);
+  location_t m_field = make_location (start, start, finish);
+  rich_location richloc (line_table, m_field);
+  source_range range;
+  range.m_start = start;
+  range.m_finish = finish;
+  richloc.add_fixit_replace (range, "field");
+
+  edit_context edit;
+  edit.add_fixits (&richloc);
+  const char *result = edit.get_content (filename);
+  if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
+    {
+      ASSERT_STREQ ("/* before */\n"
+		    "foo = bar.field;\n"
+		    "/* after */\n", result);
+
+      // TODO: verify effective locations
+
+      /* Verify diff.  */
+      char *diff = edit.generate_diff ();
+      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
+		    " /* before */\n"
+		    "-foo = bar.m_field;\n"
+		    "+foo = bar.field;\n"
+		    " /* after */\n", diff);
+      free (diff);
+    }
+}
+
+/* FIXME.  */
+
+static void
+test_applying_fixits_remove (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     .....................000000000111111111.
+     .....................123456789012345678.  */
+  const char *content = ("/* before */\n"
+			 "foo = bar.m_field;\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt (case_);
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  /* Remove ".m_field".  */
+  location_t start = linemap_position_for_column (line_table, 10);
+  location_t finish = linemap_position_for_column (line_table, 17);
+  rich_location richloc (line_table, start);
+  source_range range;
+  range.m_start = start;
+  range.m_finish = finish;
+  richloc.add_fixit_remove (range);
+
+  edit_context edit;
+  edit.add_fixits (&richloc);
+  const char *result = edit.get_content (filename);
+  if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
+    {
+      ASSERT_STREQ ("/* before */\n"
+		    "foo = bar;\n"
+		    "/* after */\n", result);
+
+      // TODO: verify effective locations
+      /* Verify diff.  */
+      char *diff = edit.generate_diff ();
+      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
+		    " /* before */\n"
+		    "-foo = bar.m_field;\n"
+		    "+foo = bar;\n"
+		    " /* after */\n", diff);
+      free (diff);
+    }
+}
+
+/* FIXME.  */
+
+static void
+test_applying_fixits_multiple (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     .....................00000000011111111.
+     .....................12345678901234567.  */
+  const char *content = ("/* before */\n"
+			 "foo = bar.field;\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt (case_);
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  location_t c7 = linemap_position_for_column (line_table, 7);
+  location_t c9 = linemap_position_for_column (line_table, 9);
+  location_t c11 = linemap_position_for_column (line_table, 11);
+  location_t c15 = linemap_position_for_column (line_table, 15);
+  location_t c17 = linemap_position_for_column (line_table, 17);
+
+  if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  /* Add a comment in front of "bar.field".  */
+  rich_location insert_a (line_table, c7);
+  insert_a.add_fixit_insert (c7, "/* alpha */");
+
+  /* Add a comment after "bar.field;".  */
+  rich_location insert_b (line_table, c17);
+  insert_b.add_fixit_insert (c17, "/* beta */");
+
+  /* Replace "bar" with "pub".   */
+  rich_location replace_a (line_table, c7);
+  replace_a.add_fixit_replace (source_range::from_locations (c7, c9),
+			     "pub");
+
+  /* Replace "field" with "meadow".   */
+  rich_location replace_b (line_table, c7);
+  replace_b.add_fixit_replace (source_range::from_locations (c11, c15),
+			     "meadow");
+
+  /* TODO: do in any order.  */
+  edit_context edit;
+  edit.add_fixits (&insert_a);
+  ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
+  ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
+  ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
+  ASSERT_EQ (18, edit.get_effective_column (filename, 2, 7));
+  ASSERT_EQ (27, edit.get_effective_column (filename, 2, 16));
+  ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
+
+  edit.add_fixits (&insert_b);
+  edit.add_fixits (&replace_a);
+  edit.add_fixits (&replace_b);
+
+  if (c17 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
+    {
+      const char *result = edit.get_content (filename);
+      // FIXME: 0-termination!!!
+      ASSERT_STREQ ("/* before */\n"
+		     "foo = /* alpha */pub.meadow;/* beta */\n"
+		     "/* after */\n", result);
+      /* Verify diff.  */
+      char *diff = edit.generate_diff ();
+      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
+		    " /* before */\n"
+		    "-foo = bar.field;\n"
+		    "+foo = /* alpha */pub.meadow;/* beta */\n"
+		    " /* after */\n", diff);
+      free (diff);
+    }
+}
+
+/* FIXME.  */
+
+static void
+change_line (edit_context &edit, int line_num)
+{
+  const line_map_ordinary *ord_map
+    = LINEMAPS_LAST_ORDINARY_MAP (line_table);
+  const int column = 1;
+  location_t loc =
+    linemap_position_for_line_and_column (line_table, ord_map,
+					  line_num, column);
+
+  expanded_location exploc = expand_location (loc);
+  ASSERT_EQ (line_num, exploc.line);
+  ASSERT_EQ (column, exploc.column);
+
+  rich_location insert (line_table, loc);
+  insert.add_fixit_insert (loc, "CHANGED: ");
+  edit.add_fixits (&insert);
+}
+
+/* FIXME.  */
+
+static void
+test_applying_fixits_multiple_lines ()
+{
+  /* Create a tempfile and write many lines of text to it.  */
+  named_temp_file tmp (".txt");
+  const char *filename = tmp.get_filename ();
+  FILE *f = fopen (filename, "w");
+  ASSERT_NE (f, NULL);
+  for (int i = 1; i <= 1000; i++)
+    fprintf (f, "line %i\n", i);
+  fclose (f);
+
+  line_table_test ltt;
+  linemap_add (line_table, LC_ENTER, false, filename, 1);
+  linemap_position_for_column (line_table, 127);
+
+  edit_context edit;
+
+  /* A run of consecutive lines.  */
+  change_line (edit, 2);
+  change_line (edit, 3);
+  change_line (edit, 4);
+
+  /* A run of nearby lines, within the contextual limit.  */
+  change_line (edit, 150);
+  change_line (edit, 151);
+  change_line (edit, 153);
+
+  /* Verify diff.  */
+  char *diff = edit.generate_diff (); // 3, false);
+  ASSERT_STREQ ("@@ -1,7 +1,7 @@\n"
+		" line 1\n"
+		"-line 2\n"
+		"-line 3\n"
+		"-line 4\n"
+		"+CHANGED: line 2\n"
+		"+CHANGED: line 3\n"
+		"+CHANGED: line 4\n"
+		" line 5\n"
+		" line 6\n"
+		" line 7\n"
+		"@@ -147,10 +147,10 @@\n"
+		" line 147\n"
+		" line 148\n"
+		" line 149\n"
+		"-line 150\n"
+		"-line 151\n"
+		"+CHANGED: line 150\n"
+		"+CHANGED: line 151\n"
+		" line 152\n"
+		"-line 153\n"
+		"+CHANGED: line 153\n"
+		" line 154\n"
+		" line 155\n"
+		" line 156\n", diff);
+  free (diff);
+
+  // FIXME: keep it alive:
+  tmp.get_filename ();
+}
+
+/* FIXME.  */
+
+static void
+test_applying_fixits_modernize_named_init ()
+{
+  /* Create a tempfile and write some text to it.
+     .....................00000000011111111.
+     .....................12345678901234567.  */
+  const char *content = ("/* before */\n"
+			 "bar    : 1,\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt ();
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  location_t c1 = linemap_position_for_column (line_table, 1);
+  location_t c3 = linemap_position_for_column (line_table, 3);
+  location_t c8 = linemap_position_for_column (line_table, 8);
+
+  /* Replace "bar" with ".".  */
+  rich_location r1 (line_table, c8);
+  r1.add_fixit_replace (source_range::from_locations (c1, c3),
+			".");
+
+  /* Replace ":" with "bar =".   */
+  rich_location r2 (line_table, c8);
+  r2.add_fixit_replace (source_range::from_locations (c8, c8),
+			"bar =");
+
+  /* The order should not matter.  Do r1 then r2. */
+  {
+    edit_context edit;
+    edit.add_fixits (&r1);
+
+    /* Verify state after first replacement.  */
+    {
+      /* We should now have:
+	 ............00000000011.
+	 ............12345678901.  */
+      ASSERT_STREQ ("/* before */\n"
+		    ".    : 1,\n"
+		    "/* after */\n",
+		    edit.get_content (filename));
+      /* Location of the "1".  */
+      ASSERT_EQ (6, edit.get_effective_column (filename, 2, 8));
+      /* Location of the ",".  */
+      ASSERT_EQ (9, edit.get_effective_column (filename, 2, 11));
+    }
+
+    edit.add_fixits (&r2);
+
+    /* Verify state after second replacement.
+       ............00000000011111111.
+       ............12345678901234567.  */
+    ASSERT_STREQ ("/* before */\n"
+		  ".    bar = 1,\n"
+		  "/* after */\n",
+		  edit.get_content (filename));
+  }
+
+  /* Try again, doing r2 then r1; the result should be the same.  */
+  {
+    edit_context edit;
+    edit.add_fixits (&r2);
+    edit.add_fixits (&r1);
+    /*.............00000000011111111.
+      .............12345678901234567.  */
+    ASSERT_STREQ ("/* before */\n"
+		  ".    bar = 1,\n"
+		  "/* after */\n",
+		  edit.get_content (filename));
+  }
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+edit_context_c_tests ()
+{
+  for_each_line_table_case (test_applying_fixits_insert);
+  for_each_line_table_case (test_applying_fixits_growing_replace);
+  for_each_line_table_case (test_applying_fixits_shrinking_replace);
+  for_each_line_table_case (test_applying_fixits_remove);
+  for_each_line_table_case (test_applying_fixits_multiple);
+  test_applying_fixits_modernize_named_init ();
+  test_applying_fixits_multiple_lines ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/edit-context.h b/gcc/edit-context.h
new file mode 100644
index 0000000..a719706
--- /dev/null
+++ b/gcc/edit-context.h
@@ -0,0 +1,55 @@
+/* Applying fix-its on behalf of the user to their code.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_EDIT_CONTEXT_H
+#define GCC_EDIT_CONTEXT_H
+
+#include "typed-splay-tree.h"
+
+class edit_context;
+class edited_file;
+
+/* A set of changes to the source code.  */
+
+class edit_context
+{
+ public:
+  edit_context ();
+
+  void add_fixits (rich_location *richloc);
+
+  const char *get_content (const char *filename);
+
+  int get_effective_column (const char *filename, int line, int column);
+
+  char *generate_diff ();
+  void print_diff (pretty_printer *pp);
+  bool apply_changes ();
+
+ private:
+  bool apply_insert (const fixit_insert *insert);
+  bool apply_replace (const fixit_replace *replace);
+  edited_file *get_file (const char *filename);
+  edited_file &get_or_insert_file (const char *filename);
+
+  bool m_valid;
+  typed_splay_tree<const char *, edited_file *> m_files;
+};
+
+#endif /* GCC_EDIT_CONTEXT_H.  */
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index 6453e31..902bcda 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -67,6 +67,7 @@ selftest::run_tests ()
      rely on.  */
   diagnostic_show_locus_c_tests ();
   diagnostic_c_tests ();
+  edit_context_c_tests ();
   fold_const_c_tests ();
   spellcheck_c_tests ();
   spellcheck_tree_c_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index fafd237..ceffc6c 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -158,6 +158,7 @@ extern char *read_file (const location &loc, const char *path);
 extern void bitmap_c_tests ();
 extern void diagnostic_c_tests ();
 extern void diagnostic_show_locus_c_tests ();
+extern void edit_context_c_tests ();
 extern void et_forest_c_tests ();
 extern void fold_const_c_tests ();
 extern void fibonacci_heap_c_tests ();
-- 
1.8.5.3

