diff --git a/fast-tags.cabal b/fast-tags.cabal
--- a/fast-tags.cabal
+++ b/fast-tags.cabal
@@ -1,5 +1,5 @@
 name: fast-tags
-version: 0.0.4
+version: 0.0.5
 cabal-version: >= 1.8
 build-type: Simple
 synopsis: Fast incremental vi tags.
@@ -11,11 +11,17 @@
     In addition, it will load an existing tags file and merge generated tags.
     .
     The intent is to bind it to vim's BufWrite autocommand to automatically
-    keep the tags file up to date.
+    keep the tags file up to date.  This only works for files changed be the
+    editor of course, so you may want to bind 'rm tags' to a 'pull' posthook.
     .
+    Changes since 0.0.4:
+    .
+    * Tags with the same name are sorted by their type: Function, Type,
+    Constructor, Class, Module.
+    .
     Changes since 0.0.3:
     .
-    * Fixed bug that prevented old tags from being filered out.
+    * Fixed bug that prevented old tags from being filtered out.
     .
     Changes since 0.0.2:
     .
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -39,19 +39,25 @@
         verbose = Verbose `elem` flags
     oldTags <- fmap (maybe [vimMagicLine] T.lines) $
         catchENOENT $ Text.IO.readFile output
-    newTags <- fmap concat $ forM (zip [0..] inputs) $ \(i :: Int, fn) -> do
-        tags <- processFile fn
-        -- This has the side-effect of forcing the the tags, which is essential
-        -- if I'm tagging a lot of files at once.
-        let (warnings, newTags) = Either.partitionEithers tags
-        forM_ warnings $ \warn -> do
-            IO.hPutStrLn IO.stderr warn
-        when verbose $ do
-            let line = show i ++ " of " ++ show (length inputs - 1)
-                    ++ ": " ++ fn
-            putStr $ '\r' : line ++ replicate (78 - length line) ' '
-            IO.hFlush IO.stdout
-        return newTags
+    -- This will merge and sort the new tags.  But I don't run it on the
+    -- the result of merging the old and new tags, so tags from another
+    -- file won't be sorted properly.  To do that I'd have to parse all the
+    -- old tags and run processAll on all of them, which is a hassle.
+    -- TODO try it and see if it really hurts performance that much.
+    newTags <- fmap (processAll . concat) $
+        forM (zip [0..] inputs) $ \(i :: Int, fn) -> do
+            tags <- processFile fn
+            -- This has the side-effect of forcing the the tags, which is
+            -- essential if I'm tagging a lot of files at once.
+            let (warnings, newTags) = Either.partitionEithers tags
+            forM_ warnings $ \warn -> do
+                IO.hPutStrLn IO.stderr warn
+            when verbose $ do
+                let line = show i ++ " of " ++ show (length inputs - 1)
+                        ++ ": " ++ fn
+                putStr $ '\r' : line ++ replicate (78 - length line) ' '
+                IO.hFlush IO.stdout
+            return newTags
     when verbose $ putChar '\n'
 
     let write = if output == "-" then Text.IO.hPutStr IO.stdout
@@ -63,11 +69,10 @@
 
 mergeTags :: [FilePath] -> [Text] -> [Pos TagVal] -> [Text]
 mergeTags inputs old new =
-    merge (List.sort (map showTag new)) (filter (not . isNewTag textFns) old)
-    where
-    -- Turns out GHC will not float out the T.pack and it makes a big
-    -- performance difference.
-    textFns = Set.fromList $ map T.pack inputs
+    -- 'new' was already been sorted by 'process', but then I just concat
+    -- the tags from each file, so they need sorting again.
+    merge (map showTag new) (filter (not . isNewTag textFns) old)
+    where textFns = Set.fromList $ map T.pack inputs
 
 data Flag = Output FilePath | Verbose
     deriving (Eq, Show)
@@ -152,13 +157,41 @@
 data SrcPos = SrcPos {
     posFile :: !FilePath
     , posLine :: !Int
-    }
+    } deriving (Eq)
 
 instance (Show a) => Show (Pos a) where
     show (Pos pos val) = show pos ++ ":" ++ show val
 instance Show SrcPos where
     show (SrcPos fn line) = fn ++ ":" ++ show line
 
+-- * process
+
+-- | Global processing for when all tags are together.
+processAll :: [Pos TagVal] -> [Pos TagVal]
+processAll = sortDups . dropDups (\t -> (posOf t, tagText t))
+    . sortOn tagText
+
+-- | Given multiple matches, vim will jump to the first one.  So sort adjacent
+-- tags with the same text by their type.
+--
+-- Mostly this is so that given a type with the same name as its module,
+-- the type will come first.
+sortDups :: [Pos TagVal] -> [Pos TagVal]
+sortDups = concat . sort . List.groupBy (\a b -> tagText a == tagText b)
+    where
+    sort = map (sortOn key)
+    key :: Pos TagVal -> Int
+    key (Pos _ (Tag _ typ)) = case typ of
+        Function -> 0
+        Type -> 1
+        Constructor -> 2
+        Class -> 3
+        Module -> 4
+
+tagText :: Pos TagVal -> Text
+tagText (Pos _ (Tag text _)) = text
+
+-- | Read tags from one file.
 processFile :: FilePath -> IO [Tag]
 processFile fn = fmap (process fn) (Text.IO.readFile fn)
     `Exception.catch` \(exc :: Exception.SomeException) -> do
@@ -168,12 +201,10 @@
             ++ show exc
         return []
 
+-- | Process one file's worth of tags.
 process :: FilePath -> Text -> [Tag]
-process fn = dropDups tagText . concatMap blockTags . breakBlocks
-    . stripComments . Monoid.mconcat . map tokenize . stripCpp . annotate fn
-    where
-    tagText (Right (Pos _ (Tag text _))) = text
-    tagText (Left warn) = T.pack warn
+process fn = concatMap blockTags . breakBlocks . stripComments
+    . Monoid.mconcat . map tokenize . stripCpp . annotate fn
 
 -- * tokenize
 
@@ -461,3 +492,6 @@
         | key a == key b = go a bs
         | otherwise = a : go b bs
 dropDups _ [] = []
+
+sortOn :: (Ord k) => (a -> k) -> [a] -> [a]
+sortOn key = List.sortBy (\a b -> compare (key a) (key b))
diff --git a/src/Main_test.hs b/src/Main_test.hs
--- a/src/Main_test.hs
+++ b/src/Main_test.hs
@@ -2,8 +2,10 @@
 module Main_test where
 import qualified Control.Exception as Exception
 import Control.Monad
+import qualified Data.Either as Either
 import qualified Data.Monoid as Monoid
 import qualified Data.Text as Text
+
 import Exception (assert)
 import qualified System.IO.Unsafe as Unsafe
 
@@ -12,11 +14,11 @@
 
 
 -- This is kind of annoying without automatic test_* collection...
-main = do
-    test_tokenize
-    test_skipString
-    test_stripComments
-    test_process
+main = sequence_
+    [ test_tokenize, test_skipString, test_stripComments
+    , test_breakBlocks, test_processAll
+    , test_process
+    ]
 
 test_tokenize = do
     -- drop leading "nl 0"
@@ -61,8 +63,25 @@
     equal assert (f "1\n 11\n 11\n") [["1", "11", "11"]]
     equal assert (f " 11\n 11\n") [["11"], ["11"]]
 
+test_processAll = do
+    let f = map showTag . Main.processAll . Either.rights
+            . concatMap (\(i, t) -> Main.process ("fn" ++ show i) t)
+            . zip [0..]
+        showTag (Pos p (Tag text typ)) =
+            unwords [show p, Text.unpack text, show typ]
+    equal assert (f ["data X", "module X"])
+        ["fn0:1 X Type", "fn1:1 X Module"]
+    -- Type goes ahead of Module.
+    equal assert (f ["module X\ndata X"])
+        ["fn0:2 X Type", "fn0:1 X Module"]
+    -- Extra X was filtered.
+    equal assert (f ["module X\ndata X = X\n"])
+        ["fn0:2 X Type", "fn0:1 X Module"]
+
 test_process = sequence_
-    [test_misc, test_data, test_gadt, test_families, test_functions, test_class]
+    [ test_misc, test_data, test_gadt, test_families, test_functions
+    , test_class
+    ]
 
 test_misc = do
     let f text = [tag | Right (Pos _ tag) <- Main.process "fn" text]
@@ -81,19 +100,18 @@
 test_data = do
     let f = process
     equal assert (f "data X\n") ["X"]
-    -- The extra X is suppressed.
-    equal assert (f "data X = X Int\n") ["X"]
+    equal assert (f "data X = X Int\n") ["X", "X"]
     equal assert (f "data Foo = Bar | Baz") ["Foo", "Bar", "Baz"]
     equal assert (f "data Foo =\n\tBar\n\t| Baz") ["Foo", "Bar", "Baz"]
     -- Records.
     equal assert (f "data Foo a = Bar { field :: Field }")
         ["Foo", "Bar", "field"]
-    equal assert (f "data R = R { a::X, b::Y }") ["R", "a", "b"]
-    equal assert (f "data R = R {\n\ta::X\n\t, b::Y\n\t}") ["R", "a", "b"]
-    equal assert (f "data R = R {\n\ta,b::X\n\t}") ["R", "a", "b"]
+    equal assert (f "data R = R { a::X, b::Y }") ["R", "R", "a", "b"]
+    equal assert (f "data R = R {\n\ta::X\n\t, b::Y\n\t}") ["R", "R", "a", "b"]
+    equal assert (f "data R = R {\n\ta,b::X\n\t}") ["R", "R", "a", "b"]
 
     equal assert (f "data R = R {\n\ta :: !RealTime\n\t, b :: !RealTime\n\t}")
-        ["R", "a", "b"]
+        ["R", "R", "a", "b"]
 
 test_gadt = do
     let f = process
diff --git a/src/mt.hs b/src/mt.hs
new file mode 100644
--- /dev/null
+++ b/src/mt.hs
@@ -0,0 +1,174 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Main_test where
+import qualified Control.Exception as Exception
+import Control.Monad
+import qualified Data.Monoid as Monoid
+import qualified Data.Text as Text
+import Exception (assert)
+import qualified System.IO.Unsafe as Unsafe
+
+import qualified Main as Main
+import Main (TokenVal(..), TagVal(..), Type(..), Tag, Pos(..))
+
+
+-- This is kind of annoying without automatic test_* collection...
+main = do
+    test_tokenize
+    test_skipString
+    test_stripComments
+    test_process
+
+test_tokenize = do
+    -- drop leading "nl 0"
+    let f = drop 1 . extractTokens . tokenize
+    equal assert (f "a::b->c") ["a", "::", "b", "->", "c"]
+    equal assert (f "x{-\n  bc#-}\n")
+        ["x", "{-", "nl 2", "bc", "#", "-}"]
+    equal assert (f "X.Y") ["X.Y"]
+    equal assert (f "x9") ["x9"]
+    -- equal assert (f "9x") ["nl 0", "9", "x"]
+    equal assert (f "x :+: y") ["x", ":+:", "y"]
+    equal assert (f "(#$)") ["(#$)"]
+    equal assert (f "$#-- hi") ["$#", "--", "hi"]
+    equal assert (f "(*), (-)") ["(*)", ",", "(-)"]
+
+test_skipString = do
+    let f = Main.skipString
+    equal assert (f "hi \" there") " there"
+    equal assert (f "hi \\a \" there") " there"
+    equal assert (f "hi \\\" there\"") ""
+    equal assert (f "hi") ""
+    -- String continuation isn't implemented yet.
+    equal assert (f "hi \\") ""
+
+test_stripComments = do
+    let f = extractTokens . Main.stripComments . tokenize
+    equal assert (f "hello -- there") ["nl 0", "hello"]
+    equal assert (f "hello {- there -} fred") ["nl 0", "hello", "fred"]
+    equal assert (f "{-# LANG #-} hello {- there {- nested -} comment -} fred")
+        ["nl 0", "hello", "fred"]
+
+test_breakBlocks = do
+    let f = map (extractTokens . Main.UnstrippedTokens . Main.stripNewlines)
+            . Main.breakBlocks . tokenize
+    equal assert (f "1\n2\n") [["1"], ["2"]]
+    equal assert (f "1\n 1\n2\n") [["1", "1"], ["2"]]
+    equal assert (f "1\n 1\n 1\n2\n") [["1", "1", "1"], ["2"]]
+    -- intervening blank lines are ignored
+    equal assert (f "1\n 1\n\n 1\n2\n") [["1", "1", "1"], ["2"]]
+    equal assert (f "1\n\n\n 1\n2\n") [["1", "1"], ["2"]]
+
+    equal assert (f "1\n 11\n 11\n") [["1", "11", "11"]]
+    equal assert (f " 11\n 11\n") [["11"], ["11"]]
+
+test_process = sequence_
+    [ test_sort_dups, test_misc, test_data, test_gadt, test_families
+    , test_functions, test_class
+    ]
+
+test_sort_dups = do
+    let f text = [tag | Right (Pos _ tag) <- Main.process "fn" text]
+    equal assert (f "module X where\ndata Y = X\ntype X\nclass X where\n")
+        [ Tag "X" Type, Tag "X" Constructor, Tag "X" Class, Tag "X" Module
+        , Tag "Y" Type
+        ]
+
+test_misc = do
+    let f text = [tag | Right (Pos _ tag) <- Main.process "fn" text]
+    equal assert (f "module Bar.Foo where\n") [Tag "Foo" Module]
+    equal assert (f "newtype Foo a b =\n\tBar x y z\n")
+        [Tag "Bar" Constructor, Tag "Foo" Type]
+    equal assert (f "f :: A -> B\ng :: C -> D\ndata D = C {\n\tf :: A\n\t}\n")
+        [Tag "C" Constructor, Tag "D" Type, Tag "f" Function,
+            Tag "f" Function, Tag "g" Function]
+
+test_unicode = do
+    let f = process
+    equal assert (f "數字 :: Int") ["數字"]
+    equal assert (f "(·), x :: Int") ["·", "x"]
+
+test_data = do
+    let f = process
+    equal assert (f "data X\n") ["X"]
+    -- The extra X is suppressed.
+    equal assert (f "data X = X Int\n") ["X"]
+    equal assert (f "data Foo = Bar | Baz") ["Bar", "Baz", "Foo"]
+    equal assert (f "data Foo =\n\tBar\n\t| Baz") ["Bar", "Baz", "Foo"]
+    -- Records.
+    equal assert (f "data Foo a = Bar { field :: Field }")
+        ["Bar", "Foo", "field"]
+    equal assert (f "data R = R { a::X, b::Y }") ["R", "a", "b"]
+    equal assert (f "data R = R {\n\ta::X\n\t, b::Y\n\t}") ["R", "a", "b"]
+    equal assert (f "data R = R {\n\ta,b::X\n\t}") ["R", "a", "b"]
+
+    equal assert (f "data R = R {\n\ta :: !RealTime\n\t, b :: !RealTime\n\t}")
+        ["R", "a", "b"]
+
+test_gadt = do
+    let f = process
+    equal assert (f "data X where A :: X\n") ["A", "X"]
+    equal assert (f "data X where\n\tA :: X\n") ["A", "X"]
+    equal assert (f "data X where\n\tA :: X\n\tB :: X\n") ["A", "B", "X"]
+    equal assert (f "data X where\n\tA, B :: X\n") ["A", "B", "X"]
+
+test_families = do
+    let f = process
+    equal assert (f "type family X :: *\n") ["X"]
+    equal assert (f "data family X :: * -> *\n") ["X"]
+    equal assert (f "class C where\n\ttype X y :: *\n") ["C", "X"]
+    equal assert (f "class C where\n\tdata X y :: *\n") ["C", "X"]
+
+test_functions = do
+    let f = process
+    -- Multiple declarations.
+    equal assert (f "a,b::X") ["a", "b"]
+    -- With an operator.
+    equal assert (f "(+), a :: X") ["+", "a"]
+    -- Don't get fooled by literals.
+    equal assert (f "1 :: Int") []
+
+test_class = do
+    let f = process
+    equal assert (f "class (X x) => C a b where\n\tm :: a->b\n\tn :: c\n")
+        ["C", "m", "n"]
+    equal assert (f "class A a where f :: X\n") ["A", "f"]
+    -- indented inside where
+    equal assert (f "class X where\n\ta, (+) :: X\n") ["+", "X", "a"]
+    equal assert (f "class X where\n\ta :: X\n\tb, c :: Y")
+        ["X", "a", "b", "c"]
+    equal assert (f "class X\n\twhere\n\ta :: X\n\tb, c :: Y")
+        ["X", "a", "b", "c"]
+    equal assert (f "class X\n\twhere\n\ta ::\n\t\tX\n\tb :: Y")
+        ["X", "a", "b"]
+
+process :: Text.Text -> [String]
+process = map untag . Main.process "fn"
+
+untag :: Tag -> String
+untag (Right (Pos _ (Tag name _))) = Text.unpack name
+untag (Left warn) = "warn: " ++ warn
+
+tokenize :: Text.Text -> Main.UnstrippedTokens
+tokenize = Monoid.mconcat . map Main.tokenize . Main.stripCpp
+    . Main.annotate "fn"
+
+plist :: (Show a) => [a] -> IO ()
+plist xs = mapM_ (putStrLn . show) xs >> putChar '\n'
+
+extractTokens :: Main.UnstrippedTokens -> [Text.Text]
+extractTokens = map (\token -> case Main.valOf token of
+    Token name -> name
+    Newline n -> Text.pack ("nl " ++ show n)) . Main.unstrippedTokensOf
+
+equal :: (Show a, Eq a) => Assert z -> a -> a -> IO ()
+equal srcpos x y = unless (x == y) $
+    putStrLn $ "__ " ++ getSourceLoc srcpos ++ " " ++ show x ++ " /= " ++ show y
+
+type Assert a = Bool -> a -> String
+
+-- | Awful ghc hack to get source line location.
+getSourceLoc :: Assert a -> String
+getSourceLoc assert_ = takeWhile (/=' ') $ Unsafe.unsafePerformIO $
+    Exception.evaluate (assert_ False (error "Impossible"))
+        `Exception.catch` (\(Exception.AssertionFailed s) -> return s)
+
