diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 566772cc740fff7ea4efeb1b8b26b068f5e1a963..0a7c7e02b37c1eec096b2d97ca7793c0193b4cf6 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -186,7 +186,8 @@ typedef char *(*diagnostic_make_option_name_cb) (const diagnostic_context *,
 						 diagnostic_t,
 						 diagnostic_t);
 typedef char *(*diagnostic_make_option_url_cb) (const diagnostic_context *,
-						int);
+						int,
+						unsigned);
 
 class edit_context;
 namespace json { class value; }
@@ -526,7 +527,8 @@ public:
   {
     if (!m_option_callbacks.m_make_option_url_cb)
       return nullptr;
-    return m_option_callbacks.m_make_option_url_cb (this, option_index);
+    return m_option_callbacks.m_make_option_url_cb (this, option_index,
+						    get_lang_mask ());
   }
 
   void
diff --git a/gcc/gcc-urlifier.cc b/gcc/gcc-urlifier.cc
index 877445c6f4098c3583a83139db0f2b9fddf79ab8..6bd176fc24834b4d3801a77601335b179dc4c198 100644
--- a/gcc/gcc-urlifier.cc
+++ b/gcc/gcc-urlifier.cc
@@ -24,6 +24,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "pretty-print.h"
 #include "pretty-print-urlifier.h"
 #include "gcc-urlifier.h"
+#include "opts.h"
+#include "options.h"
 #include "selftest.h"
 
 namespace {
@@ -34,23 +36,34 @@ namespace {
 class gcc_urlifier : public urlifier
 {
 public:
+  gcc_urlifier (unsigned int lang_mask)
+  : m_lang_mask (lang_mask)
+  {}
+
   char *get_url_for_quoted_text (const char *p, size_t sz) const final override;
 
-  const char *get_url_suffix_for_quoted_text (const char *p, size_t sz) const;
+  label_text get_url_suffix_for_quoted_text (const char *p, size_t sz) const;
   /* We use ATTRIBUTE_UNUSED as this helper is called only from ASSERTs.  */
-  const char *get_url_suffix_for_quoted_text (const char *p) const ATTRIBUTE_UNUSED;
+  label_text get_url_suffix_for_quoted_text (const char *p) const ATTRIBUTE_UNUSED;
 
 private:
+  label_text get_url_suffix_for_option (const char *p, size_t sz) const;
+
   static char *
   make_doc_url (const char *doc_url_suffix);
+
+  unsigned int m_lang_mask;
 };
 
 /* class gcc_urlifier : public urlifier.  */
 
+/* Manage a hard-coded mapping from quoted string to URL suffixes
+   in gcc-urlifier.def  */
+
 #define DOC_URL(QUOTED_TEXT, URL_SUFFIX) \
   { (QUOTED_TEXT), (URL_SUFFIX) }
 
-const struct
+static const struct
 {
   const char *quoted_text;
   const char *url_suffix;
@@ -60,32 +73,53 @@ const struct
 
 };
 
+/* Implementation of urlifier::get_url_for_quoted_text vfunc for GCC
+   diagnostics.  */
+
 char *
 gcc_urlifier::get_url_for_quoted_text (const char *p, size_t sz) const
 {
-  if (const char *url_suffix = get_url_suffix_for_quoted_text (p, sz))
-    return make_doc_url (url_suffix);
+  label_text url_suffix = get_url_suffix_for_quoted_text (p, sz);
+  if (url_suffix.get ())
+    return make_doc_url (url_suffix.get ());
   return nullptr;
 }
 
-const char *
+/* Look for a URL for the quoted string (P, SZ).
+   Return the url suffix if found, or nullptr otherwise.  */
+
+label_text
 gcc_urlifier::get_url_suffix_for_quoted_text (const char *p, size_t sz) const
 {
-  /* Binary search.  This assumes that the quoted_text fields of doc_urls
+  if (sz == 0)
+    return label_text ();
+
+  /* If this is an option, look up the option and see if we have
+     a URL for it.  */
+  if (p[0] == '-')
+    {
+      label_text suffix = get_url_suffix_for_option (p, sz);
+      if (suffix.get ())
+	return suffix;
+    }
+
+  /* Otherwise, look within the hard-coded data table in gcc-urlifier.def.
+
+     Binary search.  This assumes that the quoted_text fields of doc_urls
      are in sorted order.  */
   int min = 0;
   int max = ARRAY_SIZE (doc_urls) - 1;
   while (true)
     {
       if (min > max)
-	return nullptr;
+	return label_text ();
       int midpoint = (min + max) / 2;
       gcc_assert ((size_t)midpoint < ARRAY_SIZE (doc_urls));
       int cmp = strncmp (p, doc_urls[midpoint].quoted_text, sz);
       if (cmp == 0)
 	{
 	  if (doc_urls[midpoint].quoted_text[sz] == '\0')
-	    return doc_urls[midpoint].url_suffix;
+	    return label_text::borrow (doc_urls[midpoint].url_suffix);
 	  else
 	    max = midpoint - 1;
 	}
@@ -94,15 +128,45 @@ gcc_urlifier::get_url_suffix_for_quoted_text (const char *p, size_t sz) const
       else
 	min = midpoint + 1;
     }
-  return nullptr;
+
+  /* Not found.  */
+  return label_text ();
 }
 
-const char *
+/* For use in selftests.  */
+
+label_text
 gcc_urlifier::get_url_suffix_for_quoted_text (const char *p) const
 {
   return get_url_suffix_for_quoted_text (p, strlen (p));
 }
 
+/* Look for a URL for the quoted string (P, SZ) that appears to be
+   an option.
+   Return the url suffix if found, or nullptr otherwise.  */
+
+label_text
+gcc_urlifier::get_url_suffix_for_option (const char *p, size_t sz) const
+{
+  /* Look up this option
+
+     find_opt does a binary search, taking a 0-terminated string,
+     and skipping the leading '-'.
+
+     We have a (pointer,size) pair that doesn't necessarily have a
+     terminator, so create a 0-terminated clone of the string.  */
+  gcc_assert (sz > 0);
+  char *tmp = xstrndup (p + 1, sz - 1); // skip the leading '-'
+  size_t opt = find_opt (tmp, m_lang_mask);
+  free (tmp);
+
+  if (opt >= N_OPTS)
+    /* Option not recognized.  */
+    return label_text ();
+
+  return get_option_url_suffix (opt, m_lang_mask);
+}
+
 char *
 gcc_urlifier::make_doc_url (const char *doc_url_suffix)
 {
@@ -115,9 +179,9 @@ gcc_urlifier::make_doc_url (const char *doc_url_suffix)
 } // anonymous namespace
 
 urlifier *
-make_gcc_urlifier ()
+make_gcc_urlifier (unsigned int lang_mask)
 {
-  return new gcc_urlifier ();
+  return new gcc_urlifier (lang_mask);
 }
 
 #if CHECKING_P
@@ -137,22 +201,26 @@ gcc_urlifier_cc_tests ()
 			doc_urls[idx].quoted_text)
 		< 0);
 
-  gcc_urlifier u;
+  gcc_urlifier u (0);
 
-  ASSERT_EQ (u.get_url_suffix_for_quoted_text (""), nullptr);
-  ASSERT_EQ (u.get_url_suffix_for_quoted_text (")"), nullptr);
+  ASSERT_EQ (u.get_url_suffix_for_quoted_text ("").get (), nullptr);
+  ASSERT_EQ (u.get_url_suffix_for_quoted_text (")").get (), nullptr);
 
-  ASSERT_STREQ (u.get_url_suffix_for_quoted_text ("#pragma message"),
+  ASSERT_STREQ (u.get_url_suffix_for_quoted_text ("#pragma message").get (),
 		"gcc/Diagnostic-Pragmas.html");
 
   // Incomplete prefix of a quoted_text
-  ASSERT_EQ (u.get_url_suffix_for_quoted_text ("#pragma mess"), nullptr);
+  ASSERT_EQ (u.get_url_suffix_for_quoted_text ("#pragma mess").get (), nullptr);
 
   /* Check that every element is findable.  */
   for (size_t idx = 0; idx < ARRAY_SIZE (doc_urls); idx++)
     ASSERT_STREQ
-      (u.get_url_suffix_for_quoted_text (doc_urls[idx].quoted_text),
+      (u.get_url_suffix_for_quoted_text (doc_urls[idx].quoted_text).get (),
        doc_urls[idx].url_suffix);
+
+  /* Check an option.  */
+  ASSERT_STREQ (u.get_url_suffix_for_quoted_text ("-fpack-struct").get (),
+		"gcc/Code-Gen-Options.html#index-fpack-struct");
 }
 
 } // namespace selftest
diff --git a/gcc/gcc-urlifier.def b/gcc/gcc-urlifier.def
index 360de930e9ec84cbb716b57ebed90b67823ba003..de6d9a3eb9624ffc972c5bb205ffe3e96fcd7235 100644
--- a/gcc/gcc-urlifier.def
+++ b/gcc/gcc-urlifier.def
@@ -16,5 +16,3 @@ DOC_URL ("#pragma pack", "gcc/Structure-Layout-Pragmas.html"),
 DOC_URL ("#pragma redefine_extname", "gcc/Symbol-Renaming-Pragmas.html"),
 DOC_URL ("#pragma scalar_storage_order", "gcc/Structure-Layout-Pragmas.html"),
 DOC_URL ("#pragma weak", "gcc/Weak-Pragmas.html"),
-DOC_URL ("--version", "gcc/Overall-Options.html#index-version"),
-DOC_URL ("-fpack-struct", "gcc/Code-Gen-Options.html#index-fpack-struct"),
diff --git a/gcc/gcc-urlifier.h b/gcc/gcc-urlifier.h
index 601cea5d0ce8c221f13ee5ddce4f3f5ee368ed1d..660d4f8828e7fde23964f9f04c4c4cee5f1e3d8f 100644
--- a/gcc/gcc-urlifier.h
+++ b/gcc/gcc-urlifier.h
@@ -21,6 +21,6 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_GCC_URLIFIER_H
 #define GCC_GCC_URLIFIER_H
 
-extern urlifier *make_gcc_urlifier ();
+extern urlifier *make_gcc_urlifier (unsigned int lang_mask);
 
 #endif /* GCC_GCC_URLIFIER_H */
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index 76081b9970d22775c3e1a819ef359cc3b011b13e..588a0e970daab8b34f14af7631ab669a0c6567d4 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -8340,7 +8340,7 @@ driver::global_initializations ()
   diagnostic_initialize (global_dc, 0);
   diagnostic_color_init (global_dc);
   diagnostic_urls_init (global_dc);
-  global_dc->set_urlifier (make_gcc_urlifier ());
+  global_dc->set_urlifier (make_gcc_urlifier (0));
 
 #ifdef GCC_DRIVER_HOST_INITIALIZATION
   /* Perform host dependent initialization when needed.  */
diff --git a/gcc/opts-diagnostic.h b/gcc/opts-diagnostic.h
index e8bb77e8594648db4ff1f03cb1410bc8141df07b..a18c4c17f6079e2e66e614738442d22a30bd078a 100644
--- a/gcc/opts-diagnostic.h
+++ b/gcc/opts-diagnostic.h
@@ -24,6 +24,7 @@ extern char *option_name (const diagnostic_context *context, int option_index,
 			  diagnostic_t orig_diag_kind, diagnostic_t diag_kind);
 
 extern char *get_option_url (const diagnostic_context *context,
-			     int option_index);
+			     int option_index,
+			     unsigned lang_mask);
 
 #endif
diff --git a/gcc/opts.cc b/gcc/opts.cc
index da6d634ebcec1598b5f52562fec11b435ac4813c..3333600e0ea24bdfd0a097de3d74b94105831404 100644
--- a/gcc/opts.cc
+++ b/gcc/opts.cc
@@ -3710,14 +3710,6 @@ get_option_html_page (int option_index)
 {
   const cl_option *cl_opt = &cl_options[option_index];
 
-  /* Analyzer options are on their own page.  */
-  if (strstr (cl_opt->opt_text, "analyzer-"))
-    return "gcc/Static-Analyzer-Options.html";
-
-  /* Handle -flto= option.  */
-  if (strstr (cl_opt->opt_text, "flto"))
-    return "gcc/Optimize-Options.html";
-
 #ifdef CL_Fortran
   if ((cl_opt->flags & CL_Fortran) != 0
       /* If it is option common to both C/C++ and Fortran, it is documented
@@ -3730,32 +3722,49 @@ get_option_html_page (int option_index)
     return "gfortran/Error-and-Warning-Options.html";
 #endif
 
-  return "gcc/Warning-Options.html";
+  return nullptr;
+}
+
+/* Get the url within the documentation for this option, or NULL.  */
+
+label_text
+get_option_url_suffix (int option_index, unsigned lang_mask)
+{
+  if (const char *url = get_opt_url_suffix (option_index, lang_mask))
+
+    return label_text::borrow (url);
+
+  /* Fallback code for some options that aren't handled byt opt_url_suffixes
+     e.g. links below "gfortran/".  */
+  if (const char *html_page = get_option_html_page (option_index))
+    return label_text::take
+      (concat (html_page,
+	       /* Expect an anchor of the form "index-Wfoo" e.g.
+		  <a name="index-Wformat"></a>, and thus an id within
+		  the page of "#index-Wformat".  */
+	       "#index",
+	       cl_options[option_index].opt_text,
+	       NULL));
+
+  return label_text ();
 }
 
 /* Return malloced memory for a URL describing the option OPTION_INDEX
    which enabled a diagnostic (context CONTEXT).  */
 
 char *
-get_option_url (const diagnostic_context *, int option_index)
+get_option_url (const diagnostic_context *,
+		int option_index,
+		unsigned lang_mask)
 {
   if (option_index)
-    return concat (/* DOCUMENTATION_ROOT_URL should be supplied via
-		      #include "config.h" (see --with-documentation-root-url),
-		      and should have a trailing slash.  */
-		   DOCUMENTATION_ROOT_URL,
-
-		   /* get_option_html_page will return something like
-		      "gcc/Warning-Options.html".  */
-		   get_option_html_page (option_index),
-
-		   /* Expect an anchor of the form "index-Wfoo" e.g.
-		      <a name="index-Wformat"></a>, and thus an id within
-		      the URL of "#index-Wformat".  */
-		   "#index", cl_options[option_index].opt_text,
-		   NULL);
-  else
-    return NULL;
+    {
+      label_text url_suffix = get_option_url_suffix (option_index, lang_mask);
+      if (url_suffix.get ())
+	return concat (DOCUMENTATION_ROOT_URL, url_suffix.get (), nullptr);
+    }
+
+  return nullptr;
 }
 
 /* Return a heap allocated producer with command line options.  */
@@ -3886,17 +3895,35 @@ gen_producer_string (const char *language_string, cl_decoded_option *options,
 
 namespace selftest {
 
-/* Verify that get_option_html_page works as expected.  */
+/* Verify that get_option_url_suffix works as expected.  */
 
 static void
-test_get_option_html_page ()
+test_get_option_url_suffix ()
 {
-  ASSERT_STREQ (get_option_html_page (OPT_Wcpp), "gcc/Warning-Options.html");
-  ASSERT_STREQ (get_option_html_page (OPT_Wanalyzer_double_free),
-	     "gcc/Static-Analyzer-Options.html");
+  ASSERT_STREQ (get_option_url_suffix (OPT_Wcpp, 0).get (),
+		"gcc/Warning-Options.html#index-Wcpp");
+  ASSERT_STREQ (get_option_url_suffix (OPT_Wanalyzer_double_free, 0).get (),
+		"gcc/Static-Analyzer-Options.html#index-Wanalyzer-double-free");
+
+  /* Test of a D-specific option.  */
+#ifdef CL_D
+  ASSERT_EQ (get_option_url_suffix (OPT_fbounds_check_, 0).get (), nullptr);
+  ASSERT_STREQ (get_option_url_suffix (OPT_fbounds_check_, CL_D).get (),
+		"gdc/Runtime-Options.html#index-fbounds-check");
+
+  /* Test of a D-specific override to an option URL.  */
+  /* Generic URL.  */
+  ASSERT_STREQ (get_option_url_suffix (OPT_fmax_errors_, 0).get (),
+		"gcc/Warning-Options.html#index-fmax-errors");
+  /* D-specific URL.  */
+  ASSERT_STREQ (get_option_url_suffix (OPT_fmax_errors_, CL_D).get (),
+		"gdc/Warnings.html#index-fmax-errors");
+#endif
+
 #ifdef CL_Fortran
-  ASSERT_STREQ (get_option_html_page (OPT_Wline_truncation),
-		"gfortran/Error-and-Warning-Options.html");
+  ASSERT_STREQ
+    (get_option_url_suffix (OPT_Wline_truncation, CL_Fortran).get (),
+     "gfortran/Error-and-Warning-Options.html#index-Wline-truncation");
 #endif
 }
 
@@ -3959,7 +3986,7 @@ test_enum_sets ()
 void
 opts_cc_tests ()
 {
-  test_get_option_html_page ();
+  test_get_option_url_suffix ();
   test_enum_sets ();
 }
 
diff --git a/gcc/opts.h b/gcc/opts.h
index 20419e4339daa8ecf53762c5cf42c9305c5e7c2a..64f94f63d20608fa147449c8e14b27b6dfc6fe0f 100644
--- a/gcc/opts.h
+++ b/gcc/opts.h
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_OPTS_H
 #define GCC_OPTS_H
 
+#include "rich-location.h"
 #include "obstack.h"
 
 /* Specifies how a switch's VAR_VALUE relates to its FLAG_VAR.  */
@@ -569,4 +570,7 @@ struct switchstr
   bool ordering;
 };
 
+extern label_text
+get_option_url_suffix (int option_index, unsigned lang_mask);
+
 #endif
diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
index d072d661fb548e51b42710f0e114838c1a1765a6..228c21d12071ff0a759eb827040b1760b97b93dd 100644
--- a/gcc/testsuite/lib/gcc-dg.exp
+++ b/gcc/testsuite/lib/gcc-dg.exp
@@ -46,6 +46,12 @@ if { [ishost "*-*-cygwin*"] } {
   setenv LANG C.ASCII
 }
 
+# Set TERM to xterm to ensure that URL escapes are disabled.
+# This avoids issues where a diagnostic which could embed a URL
+# is emitted before -fdiagnostics-plain-output is handled, where
+# otherwise the output could be affected by the environment.
+setenv TERM xterm
+
 # Avoid sporadic data-losses with expect
 match_max -d 10000
 
diff --git a/gcc/toplev.cc b/gcc/toplev.cc
index 8bc1b69f46ce7cc235eb063b2b091a02200a1050..0c2994e85a31e2abf22aa479f6c16dd7347cd4a6 100644
--- a/gcc/toplev.cc
+++ b/gcc/toplev.cc
@@ -1044,12 +1044,13 @@ general_init (const char *argv0, bool init_signals)
   global_dc->m_show_column
     = global_options_init.x_flag_show_column;
   global_dc->m_internal_error = internal_error_function;
+  const unsigned lang_mask = lang_hooks.option_lang_mask ();
   global_dc->set_option_hooks (option_enabled,
 			       &global_options,
 			       option_name,
 			       get_option_url,
-			       lang_hooks.option_lang_mask ());
-  global_dc->set_urlifier (make_gcc_urlifier ());
+			       lang_mask);
+  global_dc->set_urlifier (make_gcc_urlifier (lang_mask));
 
   if (init_signals)
     {