diff --git a/changelog/fix18361.dd b/changelog/fix18361.dd
new file mode 100644
index 000000000000..40990b9cf049
--- /dev/null
+++ b/changelog/fix18361.dd
@@ -0,0 +1,37 @@
+Fix issue 18361 - Ddoc ability to opt-out of automatic keyword highlighting in running text
+
+Currently, ddoc automatically highlights all occurrences of words in running
+text that coincide with the symbol or module being documented, or a parameter
+name of a function being documented. While convenient, it often caused
+unintended highlighting of normal words in text when module, function, or
+parameter identifiers coincide with normal words. This led to a proliferation
+of prefixing words with `_` in order to suppress this behaviour.
+
+Now a better solution has been implemented to completely opt-out of this
+feature via the `DDOC_AUTO_PSYMBOL`, `DDOC_AUTO_KEYWORD`, and `DDOC_AUTO_PARAM`
+macros, which are used for all such automatically-highlighted words in running
+text. Occurrences of module, function, or parameter names inside code blocks
+are not included. By default, these macros simply redirect to `DDOC_PSYMBOL`,
+`DDOC_KEYWORD`, and `DDOC_PARAM`, but the user can now redefine these macros so
+that they simply expand to the word itself without any highlighting:
+
+----
+DDOC_AUTO_PSYMBOL = $0
+DDOC_AUTO_KEYWORD = $0
+DDOC_AUTO_PARAM = $0
+----
+
+Furthermore, whenever a word is prefixed with `_` to suppress automatic
+highlighting, it is now wrapped in the `DDOC_AUTO_PSYMBOL_SUPPRESS` macro. This
+is to provide users who wish to opt out of automatic highlighting an easy way
+to find all occurrences of these underscore prefixes so that they can be
+removed from the text. For example, they can redefine this macro to something
+highly-visible and easily searched for, such as:
+
+----
+DDOC_AUTO_PSYMBOL_SUPPRESS = FIXME_UNDERSCORE_PREFIX $0
+----
+
+and then search the generated documentation for the string
+`FIXME_UNDERSCORE_PREFIX` and delete the `_` prefix from all corresponding
+parts of the documentation comment text.
diff --git a/res/default_ddoc_theme.ddoc b/res/default_ddoc_theme.ddoc
index 237eff703d83..2f57fb208f6f 100644
--- a/res/default_ddoc_theme.ddoc
+++ b/res/default_ddoc_theme.ddoc
@@ -736,3 +736,7 @@ DDOC_OVERLOAD_SEPARATOR = $0
DDOC_TEMPLATE_PARAM_LIST = $0
DDOC_TEMPLATE_PARAM = $0
DDOC_LINK_AUTODETECT = $(LINK $0)
+DDOC_AUTO_PSYMBOL = $(DDOC_PSYMBOL $0)
+DDOC_AUTO_KEYWORD = $(DDOC_KEYWORD $0)
+DDOC_AUTO_PARAM = $(DDOC_PARAM $0)
+DDOC_AUTO_PSYMBOL_SUPPRESS = $0
diff --git a/src/dmd/doc.d b/src/dmd/doc.d
index c4ee147c06e5..9e81e2402d1a 100644
--- a/src/dmd/doc.d
+++ b/src/dmd/doc.d
@@ -2472,23 +2472,23 @@ extern (C++) void highlightText(Scope* sc, Dsymbols* a, OutBuffer* buf, size_t o
if (c == '_' && (i == 0 || !isdigit(*(start - 1))) && (i == buf.offset - 1 || !isReservedName(start, len)))
{
buf.remove(i, 1);
- i = j - 1;
+ i = buf.bracket(i, "$(DDOC_AUTO_PSYMBOL_SUPPRESS ", j - 1, ")") - 1;
break;
}
if (isIdentifier(a, start, len))
{
- i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
+ i = buf.bracket(i, "$(DDOC_AUTO_PSYMBOL ", j, ")") - 1;
break;
}
if (isKeyword(start, len))
{
- i = buf.bracket(i, "$(DDOC_KEYWORD ", j, ")") - 1;
+ i = buf.bracket(i, "$(DDOC_AUTO_KEYWORD ", j, ")") - 1;
break;
}
if (isFunctionParameter(a, start, len))
{
//printf("highlighting arg '%s', i = %d, j = %d\n", arg.ident.toChars(), i, j);
- i = buf.bracket(i, "$(DDOC_PARAM ", j, ")") - 1;
+ i = buf.bracket(i, "$(DDOC_AUTO_PARAM ", j, ")") - 1;
break;
}
i = j - 1;
diff --git a/test/compilable/ddoc18361.d b/test/compilable/ddoc18361.d
new file mode 100644
index 000000000000..9d095ea155a4
--- /dev/null
+++ b/test/compilable/ddoc18361.d
@@ -0,0 +1,27 @@
+// PERMUTE_ARGS:
+// REQUIRED_ARGS: -D -Dd${RESULTS_DIR}/compilable -o-
+// POST_SCRIPT: compilable/extra-files/ddocAny-postscript.sh 18361
+// REQUIRED_ARGS: -d
+
+// Test notes: 'main' is the symbol being documented (DDOC_AUTO_PSYMBOL),
+// 'arguments' is a parameter (DDOC_AUTO_PARAM), and 'false' is a keyword
+// (DDOC_AUTO_KEYWORD).
+/**
+ * The main thing this program does is nothing, and I do _not want to hear any
+ * false arguments about that!
+ *
+ * Macros:
+ * DDOC_AUTO_PSYMBOL = $0
+ * DDOC_AUTO_KEYWORD = $0
+ * DDOC_AUTO_PARAM = $0
+ * DDOC_AUTO_PSYMBOL_SUPPRESS = HALPIMBEINGSUPPRESSED $0
+ *
+ * DDOC = $(BODY)
+ * DDOC_DECL = $0
+ * DDOC_MEMBER_HEADER =
+ * DDOC_MODULE_MEMBERS = $0
+ * DDOC_MEMBER = $0
+ */
+void main(string[] arguments)
+{
+}
diff --git a/test/compilable/extra-files/ddoc18361.html b/test/compilable/extra-files/ddoc18361.html
new file mode 100644
index 000000000000..01fbebfe1207
--- /dev/null
+++ b/test/compilable/extra-files/ddoc18361.html
@@ -0,0 +1,16 @@
+
+void main(string[] arguments);
+
+
+ The main thing this program does is nothing, and I do HALPIMBEINGSUPPRESSED not want to hear any + false arguments about that! + +
+