Typesetting hashtags with natural syntax
\documentclass{article}
\usepackage{xcolor}
\usepackage[T1]{fontenc}
\begin{document}
\def\hashtag{%
\textcolor{cyan}{\#}%
\begingroup
\color{red}%
\xhashtag}
\def\xhashtag{\futurelet\tmp\xxhashtag}
\def\xyhashtag#1{\xhashtag}
\def\xxhashtag{%
\ifcat a\noexpand\tmp
\tmp\expandafter\xyhashtag
\else
\endgroup
\fi}
\newenvironment{post}[2]{%
#1 said on #2:\quote
\catcode`\#\active
\catcode`\-11 %
\catcode`\_11 %
\lccode`\~`\#%
\lowercase{\let~}\hashtag
}{%
\endquote
}
\begin{post}{Sean Allred}{yesterday}
Help me, I'm #lost.
Help me, I'm #lost!
Best ever #tex #latex #plain_text
What? #confused#lost#saving-space
\end{post}
\end{document}
There are at least a couple of ways you could do this: catcode changes or the \lowercase
'trick'. As you've started off with catcodes, I'll stick with that. What you need to remember is you are tokenizing material when you do \newenviroment
. As such, you need to change catcodes before the definition:
\documentclass{article}
\newcommand\maketag[1]{\##1}
\catcode`\"=6 %
\catcode`\#=\active
\newenvironment{post}{%
\def#""1 {\maketag{""1}}%
\catcode`\#=\active
\quote
}{%
\endquote
}
\catcode`\"=12 %
\catcode`\#=6 %
\begin{document}
\begin{post}
#hello
\end{post}
\end{document}
Notice that inside the environment we only need to change the catcode of "
: we need the definition to have "
as the parameter char, but not to read the environment. It's only #
that has special handling here. Also notice that as environments form groups there is no need to worry about resetting catcodes in the \endpost
macro (end-of-environment argument).
For contrast, the \lowercase
approach would look like
\documentclass{article}
\newcommand\maketag[1]{\##1}
\newenvironment{post}{%
\begingroup
\lccode`\~=\#
\lowercase{%
\endgroup
\def~##1}{\maketag{##1}}%
\catcode`\#=\active
\quote
}{%
\endquote
}
\begin{document}
\begin{post}
#hello
\end{post}
\end{document}
Here, I don't have to mess with catcodes beyond making #
active inside the environment. The idea here is that ~
is active anyway, so I can lower-case it into a #
(which remains active) while using a 'normal' #
for setting up the definition.
By the way, notice that in both cases we need to double the parameter char here as #1
(standard catcodes) refers to any argument for the environment itself. I didn't understand why you'd got [2]
for arguments to that, so I dropped it!
Customizable at will (but of course rather slow):
\documentclass{article}
\usepackage{xcolor,environ,xparse,l3regex}
\ExplSyntaxOn
\NewDocumentEnvironment{post}{mm}
{
\char_set_catcode_other:N \# % change the catcode not to confuse \innerpost
\char_set_catcode_other:N \_
#1~said~on~#2\tl_to_str:n {:}\quote
\innerpost % absorb the contents
}
{
\endinnerpost\endquote
}
\NewEnviron{innerpost}
{
\regex_replace_all:nnN
{ \#([[:word:]\-]*) }
{ \c{texttt}\cB\{\c{coloredhash}\1\cE\} }
\BODY
\BODY
}
\ExplSyntaxOff
\newcommand\coloredhash{\textcolor{cyan}{\#}}
\begin{document}
\begin{post}{Sean Allred}{yesterday}
Help me, I'm #lost.
Help me, I'm #lost!
Best ever #tex #latex #plain_text
What? #confused#lost#saving-space
\end{post}
\end{document}
An example of further customization, suppose you want to accept accented characters in the hashtags, with \usepackage[utf8]{inputenc}
; it's sufficient to change the search regular expression into
{ \#((\c[LA][^\~]|\_|\-)*) }
which matches any combination of letters, active characters (except for ~), the hyphen and the underscore.
A solution based on \ifcat
would need much more tests.