diff --git a/core-text.cabal b/core-text.cabal
--- a/core-text.cabal
+++ b/core-text.cabal
@@ -3,11 +3,9 @@
 -- This file has been generated from package.yaml by hpack version 0.34.4.
 --
 -- see: https://github.com/sol/hpack
---
--- hash: 530a2649aa827894f3d5d158c255950e6244709a892b3897b521eff3818a21ac
 
 name:           core-text
-version:        0.3.4.0
+version:        0.3.5.0
 synopsis:       A rope type based on a finger tree over UTF-8 fragments
 description:    A rope data type for text, built as a finger tree over UTF-8 text
                 fragments. The package also includes utiltiy functions for breaking and
@@ -32,9 +30,9 @@
 copyright:      © 2018-2021 Athae Eredh Siniath and Others
 license:        MIT
 license-file:   LICENSE
+build-type:     Simple
 tested-with:
     GHC == 8.10.7
-build-type:     Simple
 extra-doc-files:
     AnsiColours.png
 
diff --git a/lib/Core/Text/Utilities.hs b/lib/Core/Text/Utilities.hs
--- a/lib/Core/Text/Utilities.hs
+++ b/lib/Core/Text/Utilities.hs
@@ -20,6 +20,7 @@
 
     -- * Helpers
     indefinite,
+    oxford,
     breakWords,
     breakLines,
     breakPieces,
@@ -55,6 +56,16 @@
 import qualified Data.FingerTree as F (ViewL (..), viewl, (<|))
 import qualified Data.List as List (dropWhileEnd, foldl', splitAt)
 import qualified Data.Text as T
+import qualified Data.Text.Short as S (
+    ShortText,
+    replicate,
+    singleton,
+    toText,
+    uncons,
+ )
+import Data.Word (Word8)
+import Language.Haskell.TH (litE, stringL)
+import Language.Haskell.TH.Quote (QuasiQuoter (QuasiQuoter))
 import Prettyprinter (
     Doc,
     LayoutOptions (LayoutOptions),
@@ -74,16 +85,6 @@
     vcat,
  )
 import Prettyprinter.Render.Text (renderLazy)
-import qualified Data.Text.Short as S (
-    ShortText,
-    replicate,
-    singleton,
-    toText,
-    uncons,
- )
-import Data.Word (Word8)
-import Language.Haskell.TH (litE, stringL)
-import Language.Haskell.TH.Quote (QuasiQuoter (QuasiQuoter))
 
 {- |
 Types which can be rendered "prettily", that is, formatted by a pretty printer
@@ -287,6 +288,43 @@
                     if c `elem` ['A', 'E', 'I', 'O', 'U', 'a', 'e', 'i', 'o', 'u']
                         then intoRope ("an " F.<| x)
                         else intoRope ("a " F.<| x)
+
+{- |
+Given a list of items (one word per Rope in the list) enumerate them with commas and
+an oxford comma before the last item. As you'd expect:
+
+@
+λ> __oxford ["one", "two", "three"]__
+"one, two, and three"
+@
+
+Because English is ridiculous, however, and we can't have nice things, two
+items are a special case:
+
+@
+λ> __oxford ["four", "five"]__
+"four and five"
+@
+
+Sadly if there is only one item you don't get an Oxford comma, either:
+
+@
+λ> __oxford ["six"]__
+"six"
+λ> __oxford []__
+""
+@
+
+-}
+oxford :: [Rope] -> Rope
+oxford [] = emptyRope
+oxford (first : []) = first
+oxford (first : second : []) = first <> " and " <> second
+oxford (first : remainder) = first <> series remainder
+  where
+    series [] = emptyRope
+    series (item : []) = ", and " <> item
+    series (item : items) = ", " <> item <> series items
 
 {- |
 Often the input text represents a paragraph, but does not have any internal
