diff --git a/cimple.cabal b/cimple.cabal
--- a/cimple.cabal
+++ b/cimple.cabal
@@ -1,5 +1,5 @@
 name:          cimple
-version:       0.0.31
+version:       0.0.32
 synopsis:      Simple C-like programming language
 homepage:      https://toktok.github.io/
 license:       GPL-3
diff --git a/src/Language/Cimple/Diagnostics.hs b/src/Language/Cimple/Diagnostics.hs
--- a/src/Language/Cimple/Diagnostics.hs
+++ b/src/Language/Cimple/Diagnostics.hs
@@ -104,7 +104,7 @@
     , cimpleLine   :: Int
     , cimpleColumn :: Int
     }
-    deriving (Show, Eq)
+    deriving (Show, Eq, Ord)
 
 instance IsPosition CimplePos where
     posFile = cimpleFile
@@ -203,8 +203,36 @@
                         maxLine = if null lineNums then 0 else maximum lineNums
                     in max 4 (length (show maxLine))
 
-                groupedSpans = groupBy ((==) `on` (posLine . spanPos)) $ sortBy (compare `on` (posLine . spanPos)) spansToShow
-                snippet = concatMap renderGrouped groupedSpans
+                posCompare s1 s2 =
+                    let p1 = spanPos s1
+                        p2 = spanPos s2
+                        isPrimary f = f == posFile p
+                    in compare (not (isPrimary (posFile p1))) (not (isPrimary (posFile p2))) <>
+                       compare (posFile p1) (posFile p2) <>
+                       compare (posLine p1) (posLine p2) <>
+                       compare (posColumn p1) (posColumn p2)
+
+                posEqLine s1 s2 =
+                    let p1 = spanPos s1
+                        p2 = spanPos s2
+                    in posFile p1 == posFile p2 && posLine p1 == posLine p2
+
+                posEqFile s1 s2 = posFile (spanPos s1) == posFile (spanPos s2)
+
+                groupedByFile = groupBy posEqFile $ sortBy posCompare spansToShow
+
+                renderFileGroup gs@(s:_) =
+                    let p_g = spanPos s
+                        sep = if posFile p_g /= posFile p
+                              then [ annotate (colorDull White) (pretty (replicate (width - 1) ' ') <> ":::") <+> annotate (bold <> color White) (pretty (posFile p_g) <> ":" <> pretty (posLine p_g) <> ":" <> pretty (posColumn p_g))
+                                   , annotate (colorDull White) (pretty (replicate width ' ') <> "|")
+                                   ]
+                              else []
+                        lineGroups = groupBy posEqLine gs
+                    in sep ++ concatMap renderGrouped lineGroups
+                renderFileGroup [] = []
+
+                snippet = concatMap renderFileGroup groupedByFile
 
                 formatFooter (l, d) =
                     let pref = case l of
diff --git a/test/Language/Cimple/DiagnosticsSpec.hs b/test/Language/Cimple/DiagnosticsSpec.hs
--- a/test/Language/Cimple/DiagnosticsSpec.hs
+++ b/test/Language/Cimple/DiagnosticsSpec.hs
@@ -313,6 +313,39 @@
                 , "    |     ^"
                 ]
 
+        it "renders multiple spans in different files" $ do
+            let cache = Map.fromList
+                    [ ("file1.c", ["line 1 in file 1"])
+                    , ("file2.h", ["line 1 in file 2"])
+                    ]
+            let diag = Diagnostic
+                    { diagPos = CimplePos "file1.c" 1 1
+                    , diagLen = 4
+                    , diagLevel = ErrorLevel
+                    , diagMsg = "error across files"
+                    , diagFlag = Nothing
+                    , diagSpans =
+                        [ DiagnosticSpan (CimplePos "file1.c" 1 1) 4 ["primary"]
+                        , DiagnosticSpan (CimplePos "file2.h" 1 1) 4 ["secondary"]
+                        ]
+                    , diagFooter = []
+                    }
+            shouldRenderTo cache [diag]
+                [ "error: error across files"
+                , "   --> file1.c:1:1"
+                , "    |"
+                , "   1| line 1 in file 1"
+                , "    | ^^^^"
+                , "    | |"
+                , "    | primary"
+                , "   ::: file2.h:1:1"
+                , "    |"
+                , "   1| line 1 in file 2"
+                , "    | ^^^^"
+                , "    | |"
+                , "    | secondary"
+                ]
+
     describe "diagToText" $ do
         it "converts diagnostic to simple text format" $ do
             let diag = Diagnostic
