Vertical list customization like enumitem's itemjoin?
Here is a version that sort of reproduces a display itemize allowing for the features of an itemize*
. Below is the same content using inlineitem
(provided in question), displayitem
and a regular itemize
for comparison purposes:
Notes:
The color red has been added to make it easier to compare the three versions. You can replace the definition of
\Show
with\newcommand*{\Show}[1]{#1}
to eliminate the highlighting or simply remove all references to the
\Show
macro.The "sort of" is because I wasn't able to get the vertical spacing of the very first item to match what
itemize
yields.
Code:
\documentclass{article}
\usepackage{enumitem}
\usepackage{xcolor}
\newcommand*{\NextLine}{%
\newline
\vspace*{0.5\baselineskip}%
\hspace*{\dimexpr\labelindent+\labelwidth-\labelsep\relax}%
}%
\newcommand*{\Show}[1]{\textcolor{red}{\bfseries#1}}% Make it easier to see difference
\newlist{inlineitem}{itemize*}{1}
\setlist[inlineitem]{ % Customize itemize* environ
before={\unskip\Show:}, % Colon before list
itemjoin={{\Show;}}, % Join with semicolons
itemjoin*={{\Show{; and}}}, % Last join has "; and "
label={}, % No label
after=., % Period at end of last item
}
\newlist{displayitem}{itemize*}{1}
\setlist[displayitem]{ % Customize itemize* environ
before={\unskip\Show:\NextLine}, % Colon before list
itemjoin={\Show;\NextLine}, % Join with semicolons
itemjoin*={\Show{; and}\NextLine}, % Last join has "; and "
label={$\bullet$}, % Label
after={\unskip\Show.\endgraf\noindent}, % Period at end of last item
}
\pagecolor{white}
\begin{document}
\noindent
Here is some text in a \verb|inlineitem|
\begin{inlineitem}
\item a list item that could go first but also last
\item another list item that I'm not sure about
\item this one should go last unless I think of another one
\end{inlineitem}
Some text after \verb|inlineitem|.
\bigskip
\noindent
Here is the same text in a \verb|displayitem|
\begin{displayitem}
\item a list item that could go first but also last
\item another list item that I'm not sure about
\item this one should go last unless I think of another one
\end{displayitem}
Some text after \verb|displayitem|.
\bigskip
\noindent
Here is the same text in an \verb|itemize|
\begin{itemize}
\item a list item that could go first but also last
\item another list item that I'm not sure about
\item this one should go last unless I think of another one
\end{itemize}
Some text after regular \verb|itemize|.
\end{document}
This answer has grown a bit since I first wrote it. I've come up with three solutions and didn't want to throw any of them away.
- The first (original) solution is simple because it doesn't add “and” to the penultimate item.
- The second solution does do this, but it is a little bit of a hack. I can't guarantee that it will continue to work if
enumitem
receives a big update. - The third solution works pretty much like
enumitem
's inline lists. It has a huge limitation though: each items should be just a single paragraph and can't contain display equations.
1. Simple solution (no “and”)
Here's a possible solution that works by redefining \item
.
This may seem a little heavy-handed, but enumitems
's inline lists do this as well.
This won't add “and” to the penultimate item (see below for that).
I'm using SetEnumitemKey
to define a new key, sentence
, that can be supplied to any list environment that enumitem
knows about.
It will append a period (.) to the last item and a semicolon (;) to every other item. You can use the itemjoin
key after sentence
to change the semicolon to something else.
\documentclass{article}
\usepackage{enumitem}
\SetEnumitemKey{sentence}{%
before*=\sentencelistprep,
after*={\unskip.},
itemjoin={;},
}
\let\sentenceitemjoin\empty
\edef\sentenceitem{\noexpand\sentenceitemjoin\unexpanded\expandafter{\item}}%
\makeatletter %% <- make @ usable in command sequences
\newcommand*\sentencelistprep{%
\def\sentenceitemjoin{\def\sentenceitemjoin{\unskip\enit@itemjoin}}%
\let\item\sentenceitem
}
\makeatother %% <- revert @
\begin{document}
Here is an \texttt{itemize} environment whose items are part of this sentence and therefore come with appropriate punctuation:
\begin{itemize}[sentence]
\item a list item that could go first but also last
\item another list item that I'm not sure about that is long enough to span two lines
\item this one should go last unless I think of another one
\end{itemize}
It's convenient because I can add, remove, and rearrange items without worrying about punctuation.
\end{document}
Warning: you can't use any other list environments inside one with the
sentence
key because the redefinition of\item
will also apply to those and will insert additional semicolons there as well.
(You probably wouldn't want to nest lists inside a running sentence, but if you have to you can add\let\sentenceitemjoin\empty
inside the inner list.)
Remarks
- I didn't include the colon (:) because does not feel like it is a part of the list (and not affected if items are reordered).
If you do want the colon to automatically be inserted you can replace the value of
before*
above by{\unskip:\sentencelistprep}
. If you don't want to have to supply the
sentence
key every time, you can create a custom list environment that includes it with e.g.\newlist{myitemize}{itemize}{1} \setlist[myitemize]{label=\textbullet,sentence}
The
before
andafter
keys overwritesentence
if supplied later, but the converse is not true becausesentence
uses the starred versionsbefore*
andafter*
.\sentencelistprep
, which is called at the start of a list withsentence
, does the following: it defines\sentenceitemjoin
as\def\sentenceitemjoin{\unskip\enit@itemjoin}
and replaces\item
by a version that includes\sentenceitemjoin
. The first\item
therefore redefines\sentenceitemjoin
to\unskip\enit@itemjoin
(the value of theitemjoin
key), and every\item
after that consequently inserts a;
before it.
2. Somewhat hacky solution (includes “and”)
Per your request, here is a way to add “; and” to the penultimate item.
I couldn't do this by simply modifying the other solution because an \item
s in a list environment can't really know if it is the final one.
The following works by first scanning the contents of the entire list environment and inserting ;
s at the end of every item but the last two, and adding ; and
to the penultimate item. It's a little bit of a hack, but it works.
You can use the itemjoin
, itemjoin*
and itemjoin**
keys to change ;
, ; and
and .
to something else.
\documentclass{article}
\usepackage{enumitem}
\usepackage{environ} %% <- for \Collect@Body
\makeatletter %% <- make @ usable in command sequences
\long\def\sentencelist@afterfi#1\fi{ %% <- insert sentencelist code after \fi (hack!)
#1\fi\Collect@Body\sentencelist %% <- apply \sentencelist to the body of the environment
}
\long\def\sentencelist#1{\@sentencelist#1\item\@sentencelist}
\long\def\@sentencelist#1\item#2\@sentencelist{
#1\@@sentencelist#2\@@sentencelist %% <- run preamble as normal
}
\long\def\@@sentencelist#1\item#2\@@sentencelist{%
\if\relax\detokenize{#2}\relax %% <- if last item
\if@newlist\else\unskip\enit@itemjoin@s\fi %% <- ..insert "; and" if not also the first item
\item #1\unskip\enit@itemjoin@ss% %% <- ..insert the \item and a "."
\else %% <- otherwise
\if@newlist\else\unskip\enit@itemjoin\fi %% <- .. insert ";" if not also the first item
\item #1% %% <- ..insert the item and a ;
\@@sentencelist#2\@@sentencelist %% <- ..and repeat
\fi
}
\let\enit@itemjoin@ss\@empty
\enitkv@key{}{itemjoin**}{% %% <- create "itemjoin**" key
\def\enit@itemjoin@ss{#1}}
\SetEnumitemKey{sentence}{% %% <- create "sentence" key
before*=\sentencelist@afterfi,
itemjoin={;}, itemjoin*={; and}, itemjoin**={.}
}
\makeatother %% <- revert @
\begin{document}
Here is an \texttt{itemize} environment whose items are part of this sentence and therefore come with appropriate punctuation:
\begin{itemize}[sentence]
\item a list item that could go first but also last
\item another list item that I'm not sure about that is long enough to span two lines
\item this one should go last unless I think of another one
\end{itemize}
It's convenient because I can add, remove, and rearrange items without worrying about punctuation.
\end{document}
3. Solution that mimicks enumitem
's inline lists
Here is a method that very closely resembles how enumitem
's inline lists work.
It's quite ingenious and I don't think I would have been able to come up with it myself.
You can customise it with the itemjoin
, itemjoin*
and itemjoin**
keys.
A big limitation is that the item's in this list can only be single paragraphs: they can't contain paragraph breaks, display equations, other lists, etc.
I don't think there's not much that can be done about this (without switching to a different method) and I suspect this is the reason why enumitem
's itemjoin
key has not been implemented for normal lists.
\documentclass{article}
\usepackage{enumitem,amsmath}
\begin{document}
\makeatletter
\newif\ifsentence@firstitem
\newcommand*\sentencebefore{%
\let\sentence@olditem\item
\let\item\sentence@item
\sentence@firstitemtrue
}
\newcommand*\sentenceafter{%
\egroup
\ifhmode\unskip\enit@itemjoin@s\fi
\sentence@olditem\unhbox\enit@inbox\unskip\enit@itemjoin@ss
}
\def\sentence@item{%
\ifhmode
\egroup
\ifsentence@firstitem
\sentence@firstitemfalse
\else
\unskip\enit@itemjoin
\fi
\sentence@olditem \unhbox\enit@inbox
\fi
\setbox\enit@inbox=\hbox\bgroup%\parshape1\@totalleftmargin\linewidth
}
\let\enit@itemjoin@ss\@empty
\enitkv@key{}{itemjoin**}{%
\def\enit@itemjoin@ss{#1}}
\makeatother
\SetEnumitemKey{sentence}{%
before*=\sentencebefore, after*=\sentenceafter,
itemjoin=;, itemjoin*={; and}, itemjoin**=.
}
Here is an \texttt{itemize} environment whose items are part of this sentence and therefore come with appropriate punctuation:
\begin{itemize}[sentence]
\item a list item that could go first but also last
\item another list item that I'm not sure about that is long enough to span two lines
\item this one should go last unless I think of another one
\end{itemize}
It's convenient because I can add, remove, and rearrange items without worrying about punctuation.
\end{document}
This does not allow for nested lists, but the list you have in mind should be the most inner level anyway.
This requires xparse
released 2019-03-05 or later (already available for MiKTeX, soon to be available with TeX Live 2019).
The main difference with inline lists is that blank lines should be taken care of, so trailing spaces and \par
tokens are removed with the \__dan_genlist_purify:N
that also puts back \item
that was removed when the body was absorbed and split. A new sequence is built and used at the end with the desired separators.
\documentclass{article}
\usepackage{enumitem,xparse}
\ExplSyntaxOn
\NewDocumentEnvironment{genlist}{mO{}+b}
{% #1 = list type, #2 = options, #3 = body
\dan_genlist_make:nnn { #1 } { #2 } { #3 }
}
{}
\seq_new:N \l_dan_genlist_body_in_seq
\seq_new:N \l_dan_genlist_body_out_seq
\clist_new:N \l_dan_genlist_opt_clist
\cs_generate_variant:Nn \seq_use:Nnnn { NVVV }
% borrow itemjoin, itemjoin* and after from enumitem;
% all other options are passed to enumitem
\keys_define:nn { dan/genlist }
{
itemjoin .tl_set:N = \l_dan_genlist_more_tl,
itemjoin* .tl_set:N = \l_dan_genlist_last_tl,
after .tl_set:N = \l_dan_genlist_after_tl,
unknown .code:n = \clist_put_right:Nx \l_dan_genlist_opt_clist
{ \l_keys_key_tl = #1 },
}
\cs_new_protected:Nn \dan_genlist_make:nnn
{
\keys_set:nn { dan/genlist } { #2 }
\seq_set_split:Nnn \l_dan_genlist_body_in_seq { \item } { #3 }
\seq_pop_left:NN \l_dan_genlist_body_in_seq \l_tmpa_tl % discard the empty item
\seq_clear:N \l_dan_genlist_body_out_seq
\seq_map_variable:NNn \l_dan_genlist_body_in_seq \l_dan_genlist_temp_tl
{
\__dan_genlist_purify:N \l_dan_genlist_temp_tl
}
\use:e { \exp_not:N \begin{#1}[ \clist_use:Nn \l_dan_genlist_opt_clist {,} ] }
\seq_use:NVVV \l_dan_genlist_body_out_seq
\l_dan_genlist_last_tl
\l_dan_genlist_more_tl
\l_dan_genlist_last_tl
\tl_use:N \l_dan_genlist_after_tl
\end{#1}
}
\cs_new_protected:Nn \__dan_genlist_purify:N
{
\regex_replace_once:nnN { \s* \c{par}* \Z } { } #1
\tl_put_left:Nn #1 { \item }
\seq_put_right:NV \l_dan_genlist_body_out_seq #1
}
\ExplSyntaxOff
\begin{document}
\begin{genlist}{itemize}[
itemjoin=;, % Join with semicolons
itemjoin*=; and, % Last join has "; and "
label={}, % No label
nosep,
after=. % Period at end of last item
]
\item a list item that could go first but also last
\item another list item that I'm not sure about
\item this one should go last unless I think of another one
\end{genlist}
\end{document}