diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,11 +1,22 @@
 # Revision history for haskell-lsp
 
+## 0.21.0.0
+
+* Stop getCompletionPrefix from crashing if beforePos is empty
+* Add DidChangeWatchedFilesRegistrationOptions
+* Add NormalizedFilePath from ghcide
+* Add diagnostic and completion tags
+* Fix language server example
+* Correctly fix the problem with '$/' notifications
+* Add azure ci
+
 ## 0.20.0.1
 
 * Fix Haddock generation syntax error
 
 ## 0.20.0.0
 
+* Force utf8 encoding when writing vfs temp files
 * Don't log errors for '$/' notifications (@jinwoo)
 * Force utf8 encoding when writing vfs temp files (@jneira)
 * Store a hash in a NormalizedUri (@mpickering)
diff --git a/example/Main.hs b/example/Main.hs
--- a/example/Main.hs
+++ b/example/Main.hs
@@ -32,7 +32,6 @@
 import           Language.Haskell.LSP.VFS
 import           System.Exit
 import qualified System.Log.Logger                     as L
-import qualified Data.Rope.UTF16                       as Rope
 
 
 -- ---------------------------------------------------------------------
@@ -64,9 +63,15 @@
       liftIO $ U.logs "main.run:dp after dispatcherProc"
       return Nothing
 
+    callbacks = Core.InitializeCallbacks
+      { Core.onInitialConfiguration = const $ Right ()
+      , Core.onConfigurationChange = const $ Right ()
+      , Core.onStartup = dp
+      }
+
   flip E.finally finalProc $ do
     Core.setupLogger (Just "/tmp/lsp-hello.log") [] L.DEBUG
-    CTRL.run (return (Right ()), dp) (lspHandlers rin) lspOptions (Just "/tmp/lsp-hello-session.log")
+    CTRL.run callbacks (lspHandlers rin) lspOptions (Just "/tmp/lsp-hello-session.log")
 
   where
     handlers = [ E.Handler ioExcept
@@ -107,7 +112,7 @@
 publishDiagnostics :: Int -> J.NormalizedUri -> J.TextDocumentVersion -> DiagnosticsBySource -> R () ()
 publishDiagnostics maxToPublish uri v diags = do
   lf <- ask
-  liftIO $ (Core.publishDiagnosticsFunc lf) maxToPublish uri v diags
+  liftIO $ Core.publishDiagnosticsFunc lf maxToPublish uri v diags
 
 -- ---------------------------------------------------------------------
 
@@ -196,11 +201,11 @@
         mdoc <- liftIO $ Core.getVirtualFileFunc lf doc
         case mdoc of
           Just (VirtualFile _version str _) -> do
-            liftIO $ U.logs $ "reactor:processing NotDidChangeTextDocument: vf got:" ++ (show $ Rope.toString str)
+            liftIO $ U.logs $ "reactor:processing NotDidChangeTextDocument: vf got:" ++ show str
           Nothing -> do
             liftIO $ U.logs "reactor:processing NotDidChangeTextDocument: vf returned Nothing"
 
-        liftIO $ U.logs $ "reactor:processing NotDidChangeTextDocument: uri=" ++ (show doc)
+        liftIO $ U.logs $ "reactor:processing NotDidChangeTextDocument: uri=" ++ show doc
 
       -- -------------------------------
 
@@ -234,7 +239,7 @@
 
       HandlerRequest (ReqHover req) -> do
         liftIO $ U.logs $ "reactor:got HoverRequest:" ++ show req
-        let J.TextDocumentPositionParams _doc pos = req ^. J.params
+        let J.TextDocumentPositionParams _doc pos _workDoneToken = req ^. J.params
             J.Position _l _c' = pos
 
         let
@@ -255,7 +260,7 @@
 
         let
           -- makeCommand only generates commands for diagnostics whose source is us
-          makeCommand (J.Diagnostic (J.Range start _) _s _c (Just "lsp-hello") _m _l) = [J.Command title cmd cmdparams]
+          makeCommand (J.Diagnostic (J.Range start _) _s _c (Just "lsp-hello") _m _t _l) = [J.Command title cmd cmdparams]
             where
               title = "Apply LSP hello command:" <> head (T.lines _m)
               -- NOTE: the cmd needs to be registered via the InitializeResponse message. See lspOptions above
@@ -266,7 +271,7 @@
                       , J.Object $ H.fromList [("start_pos",J.Object $ H.fromList [("position",    J.toJSON start)])]
                       ]
               cmdparams = Just args
-          makeCommand (J.Diagnostic _r _s _c _source _m _l) = []
+          makeCommand (J.Diagnostic _r _s _c _source _m _t _l) = []
         let body = J.List $ map J.CACommand $ concatMap makeCommand diags
             rsp = Core.makeResponseMessage req body
         reactorSend $ RspCodeAction rsp
@@ -318,6 +323,7 @@
               Nothing  -- code
               (Just "lsp-hello") -- source
               "Example diagnostic message"
+              Nothing -- tags
               (Just (J.List []))
             ]
   -- reactorSend $ J.NotificationMessage "2.0" "textDocument/publishDiagnostics" (Just r)
@@ -336,7 +342,7 @@
 
 lspOptions :: Core.Options
 lspOptions = def { Core.textDocumentSync = Just syncOptions
-                 , Core.executeCommandProvider = Just (J.ExecuteCommandOptions (J.List ["lsp-hello-command"]))
+                 , Core.executeCommandCommands = Just ["lsp-hello-command"]
                  }
 
 lspHandlers :: TChan ReactorInput -> Core.Handlers
diff --git a/haskell-lsp.cabal b/haskell-lsp.cabal
--- a/haskell-lsp.cabal
+++ b/haskell-lsp.cabal
@@ -1,5 +1,5 @@
 name:                haskell-lsp
-version:             0.20.0.1
+version:             0.21.0.0
 synopsis:            Haskell library for the Microsoft Language Server Protocol
 
 description:         An implementation of the types, and basic message server to
@@ -46,7 +46,7 @@
                      , filepath
                      , hslogger
                      , hashable
-                     , haskell-lsp-types == 0.20.*
+                     , haskell-lsp-types == 0.21.*
                      , lens >= 4.15.2
                      , mtl
                      , network-uri
diff --git a/src/Language/Haskell/LSP/Core.hs b/src/Language/Haskell/LSP/Core.hs
--- a/src/Language/Haskell/LSP/Core.hs
+++ b/src/Language/Haskell/LSP/Core.hs
@@ -433,11 +433,13 @@
           let msg = T.pack $ unwords $ ["haskell-lsp:parse error.", show json, show err] ++ _ERR_MSG_URL
           sendErrorLog tvarDat msg
   where
-    isOptionalNotification req' =
-      case (req', json) of
-        (NotCustomClient _, J.String method)
-          | "$/" `T.isPrefixOf` method -> True
-        _ -> False
+    isOptionalNotification req
+      | NotCustomClient _ <- req
+      , J.Object object <- json
+      , Just (J.String method) <- HM.lookup "method" object
+      , "$/" `T.isPrefixOf` method
+      = True
+      | otherwise = False
 
 handleInitialConfig
   :: (Show config)
diff --git a/src/Language/Haskell/LSP/VFS.hs b/src/Language/Haskell/LSP/VFS.hs
--- a/src/Language/Haskell/LSP/VFS.hs
+++ b/src/Language/Haskell/LSP/VFS.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
@@ -281,9 +282,10 @@
         curLine <- headMaybe $ T.lines $ Rope.toText
                              $ fst $ Rope.splitAtLine 1 $ snd $ Rope.splitAtLine l ropetext
         let beforePos = T.take c curLine
-        curWord <- case T.last beforePos of
-                     ' ' -> return "" -- don't count abc as the curword in 'abc '
-                     _ -> lastMaybe (T.words beforePos)
+        curWord <-
+            if | T.null beforePos -> Just ""
+               | T.last beforePos == ' ' -> Just "" -- don't count abc as the curword in 'abc '
+               | otherwise -> lastMaybe (T.words beforePos)
 
         let parts = T.split (=='.')
                       $ T.takeWhileEnd (\x -> isAlphaNum x || x `elem` ("._'"::String)) curWord
diff --git a/test/DiagnosticsSpec.hs b/test/DiagnosticsSpec.hs
--- a/test/DiagnosticsSpec.hs
+++ b/test/DiagnosticsSpec.hs
@@ -35,14 +35,14 @@
     rng = J.Range (J.Position 0 1) (J.Position 3 0)
     loc = J.Location (J.Uri "file") rng
   in
-    J.Diagnostic rng Nothing Nothing ms str (Just (J.List [J.DiagnosticRelatedInformation loc str]))
+    J.Diagnostic rng Nothing Nothing ms str Nothing (Just (J.List [J.DiagnosticRelatedInformation loc str]))
 
 mkDiagnostic2 :: Maybe J.DiagnosticSource -> Text -> J.Diagnostic
 mkDiagnostic2 ms str =
   let
     rng = J.Range (J.Position 4 1) (J.Position 5 0)
     loc = J.Location (J.Uri "file") rng
-  in J.Diagnostic rng Nothing Nothing ms str (Just (J.List [J.DiagnosticRelatedInformation loc str]))
+  in J.Diagnostic rng Nothing Nothing ms str Nothing (Just (J.List [J.DiagnosticRelatedInformation loc str]))
 
 -- ---------------------------------------------------------------------
 
diff --git a/test/JsonSpec.hs b/test/JsonSpec.hs
--- a/test/JsonSpec.hs
+++ b/test/JsonSpec.hs
@@ -36,6 +36,7 @@
     prop "MarkupContent"     (propertyJsonRoundtrip :: MarkupContent -> Property)
     prop "HoverContents"     (propertyJsonRoundtrip :: HoverContents -> Property)
     prop "ResponseMessage"   (propertyJsonRoundtrip :: ResponseMessage (Maybe ()) -> Property)
+    prop "WatchedFiles"      (propertyJsonRoundtrip :: DidChangeWatchedFilesRegistrationOptions -> Property)
 
 
 -- ---------------------------------------------------------------------
@@ -106,4 +107,15 @@
 instance (Arbitrary a) => Arbitrary (List a) where
   arbitrary = List <$> arbitrary
 
+-- ---------------------------------------------------------------------
+
+instance Arbitrary DidChangeWatchedFilesRegistrationOptions where
+  arbitrary = DidChangeWatchedFilesRegistrationOptions <$> arbitrary
+
+instance Arbitrary FileSystemWatcher where
+  arbitrary = FileSystemWatcher <$> arbitrary <*> arbitrary
+
+instance Arbitrary WatchKind where
+  arbitrary = WatchKind <$> arbitrary <*> arbitrary <*> arbitrary
+  
 -- ---------------------------------------------------------------------
diff --git a/test/URIFilePathSpec.hs b/test/URIFilePathSpec.hs
--- a/test/URIFilePathSpec.hs
+++ b/test/URIFilePathSpec.hs
@@ -2,32 +2,41 @@
 {-# LANGUAGE OverloadedStrings #-}
 module URIFilePathSpec where
 
+import Control.Monad                          (when)
 import Data.List
 #if __GLASGOW_HASKELL__ < 808
-import Data.Monoid ((<>))
+import Data.Monoid                            ((<>))
 #endif
-import Data.Text                              (pack)
+import Data.Text                              (Text, pack)
 import Language.Haskell.LSP.Types
 
-import           Network.URI
-import qualified System.FilePath.Windows as FPW
+import Network.URI
 import Test.Hspec
 import Test.QuickCheck
 #if !MIN_VERSION_QuickCheck(2,10,0)
 import Data.Char                              (GeneralCategory(..), generalCategory)
 #endif
-
+import qualified System.FilePath.Windows as FPW
+import System.FilePath                        (normalise)
+import qualified System.Info
 -- ---------------------------------------------------------------------
 
+isWindows :: Bool
+isWindows = System.Info.os == "mingw32"
+
 main :: IO ()
 main = hspec spec
 
 spec :: Spec
 spec = do
+  describe "Platform aware URI file path functions" platformAwareUriFilePathSpec
   describe "URI file path functions" uriFilePathSpec
-  describe "file path URI functions" filePathUriSpec
   describe "URI normalization functions" uriNormalizeSpec
+  describe "Normalized file path functions" normalizedFilePathSpec
 
+windowsOS :: String
+windowsOS = "mingw32"
+
 testPosixUri :: Uri
 testPosixUri = Uri $ pack "file:///home/myself/example.hs"
 
@@ -37,23 +46,14 @@
 relativePosixFilePath :: FilePath
 relativePosixFilePath = "myself/example.hs"
 
-withCurrentDirPosixFilePath :: FilePath
-withCurrentDirPosixFilePath = "/home/./myself/././example.hs"
-
 testWindowsUri :: Uri
-testWindowsUri = Uri $ pack "file:///C:/Users/myself/example.hs"
+testWindowsUri = Uri $ pack "file:///c:/Users/myself/example.hs"
 
 testWindowsFilePath :: FilePath
-testWindowsFilePath = "C:\\Users\\myself\\example.hs"
-
-testWindowsFilePathDriveLowerCase :: FilePath
-testWindowsFilePathDriveLowerCase = "c:\\Users\\myself\\example.hs"
-
-withCurrentDirWindowsFilePath :: FilePath
-withCurrentDirWindowsFilePath = "C:\\Users\\.\\myself\\.\\.\\example.hs"
+testWindowsFilePath = "c:\\Users\\myself\\example.hs"
 
-uriFilePathSpec :: Spec
-uriFilePathSpec = do
+platformAwareUriFilePathSpec :: Spec
+platformAwareUriFilePathSpec = do
   it "converts a URI to a POSIX file path" $ do
     let theFilePath = platformAwareUriToFilePath "posix" testPosixUri
     theFilePath `shouldBe` Just testPosixFilePath
@@ -70,43 +70,29 @@
     let theUri = platformAwareFilePathToUri windowsOS testWindowsFilePath
     theUri `shouldBe` testWindowsUri
 
-  it "removes unnecesary current directory paths" $ do
-    let theUri = platformAwareFilePathToUri "posix" withCurrentDirPosixFilePath
-    theUri `shouldBe` testPosixUri
-
-  it "removes unnecesary current directory paths in windows" $ do
-    let theUri = platformAwareFilePathToUri windowsOS withCurrentDirWindowsFilePath
-    theUri `shouldBe` testWindowsUri
-
-  it "make the drive letter upper case when converting a Windows file path to a URI" $ do
-    let theUri = platformAwareFilePathToUri windowsOS testWindowsFilePathDriveLowerCase
-    theUri `shouldBe` testWindowsUri
-
-filePathUriSpec :: Spec
-filePathUriSpec = do
   it "converts a POSIX file path to a URI" $ do
     let theFilePath = platformAwareFilePathToUri "posix" "./Functional.hs"
-    theFilePath `shouldBe` (Uri "file://Functional.hs")
+    theFilePath `shouldBe` (Uri "file://./Functional.hs")
 
   it "converts a Windows file path to a URI" $ do
     let theFilePath = platformAwareFilePathToUri windowsOS "./Functional.hs"
-    theFilePath `shouldBe` (Uri "file:///Functional.hs")
+    theFilePath `shouldBe` (Uri "file:///./Functional.hs")
 
   it "converts a Windows file path to a URI" $ do
     let theFilePath = platformAwareFilePathToUri windowsOS "c:/Functional.hs"
-    theFilePath `shouldBe` (Uri "file:///C:/Functional.hs")
+    theFilePath `shouldBe` (Uri "file:///c:/Functional.hs")
 
   it "converts a POSIX file path to a URI and back" $ do
     let theFilePath = platformAwareFilePathToUri "posix" "./Functional.hs"
-    theFilePath `shouldBe` (Uri "file://Functional.hs")
-    let Just (URI scheme' auth' path' query' frag') =  parseURI "file://Functional.hs"
+    theFilePath `shouldBe` (Uri "file://./Functional.hs")
+    let Just (URI scheme' auth' path' query' frag') =  parseURI "file://./Functional.hs"
     (scheme',auth',path',query',frag') `shouldBe`
       ("file:"
-      ,Just (URIAuth {uriUserInfo = "", uriRegName = "Functional.hs", uriPort = ""}) -- AZ: Seems odd
-      ,""
+      ,Just (URIAuth {uriUserInfo = "", uriRegName = ".", uriPort = ""}) -- AZ: Seems odd
+      ,"/Functional.hs"
       ,""
       ,"")
-    Just "Functional.hs" `shouldBe` platformAwareUriToFilePath "posix" theFilePath
+    Just "./Functional.hs" `shouldBe` platformAwareUriToFilePath "posix" theFilePath
 
   it "converts a Posix file path to a URI and back" $ property $ forAll genPosixFilePath $ \fp -> do
       let uri = platformAwareFilePathToUri "posix" fp
@@ -115,7 +101,9 @@
   it "converts a Windows file path to a URI and back" $ property $ forAll genWindowsFilePath $ \fp -> do
       let uri = platformAwareFilePathToUri windowsOS fp
       -- We normalise to account for changes in the path separator.
-      platformAwareUriToFilePath windowsOS uri `shouldBe` Just (FPW.normalise fp)
+      -- But driver letters are *not* normalized so we skip them
+      when (not $ "c:" `isPrefixOf` fp) $
+        platformAwareUriToFilePath windowsOS uri `shouldBe` Just (FPW.normalise fp)
 
   it "converts a relative POSIX file path to a URI and back" $ do
     let uri = platformAwareFilePathToUri "posix" relativePosixFilePath
@@ -123,15 +111,135 @@
     let back = platformAwareUriToFilePath "posix" uri
     back `shouldBe` Just relativePosixFilePath
 
+
+testUri :: Uri
+testUri | isWindows = Uri "file:///C:/Users/myself/example.hs"
+        | otherwise = Uri "file:///home/myself/example.hs"
+
+testFilePath :: FilePath
+testFilePath | isWindows = "C:\\Users\\myself\\example.hs"
+             | otherwise = "/home/myself/example.hs"
+
+withCurrentDirFilePath :: FilePath
+withCurrentDirFilePath | isWindows = "C:\\Users\\.\\myself\\.\\.\\example.hs"
+                       | otherwise = "/home/./myself/././example.hs"
+
+fromRelativefilePathUri :: Uri
+fromRelativefilePathUri | isWindows = Uri  "file:///myself/example.hs"
+                        | otherwise = Uri "file://myself/example.hs"
+
+relativeFilePath :: FilePath
+relativeFilePath | isWindows = "myself\\example.hs"
+                 | otherwise = "myself/example.hs"
+
+withLowerCaseDriveLetterFilePath :: FilePath
+withLowerCaseDriveLetterFilePath = "c:\\Users\\.\\myself\\.\\.\\example.hs"
+
+withInitialCurrentDirUriStr :: String
+withInitialCurrentDirUriStr | isWindows = "file:///Functional.hs"
+                            | otherwise = "file://Functional.hs"
+
+withInitialCurrentDirUriParts :: (String, Maybe URIAuth,  String, String, String)
+withInitialCurrentDirUriParts
+  | isWindows =
+    ("file:"
+    ,Just (URIAuth {uriUserInfo = "", uriRegName = "", uriPort = ""}) -- JNS: And asymmetrical
+    ,"/Functional.hs","","")
+  | otherwise =
+     ("file:"
+    ,Just (URIAuth {uriUserInfo = "", uriRegName = "Functional.hs", uriPort = ""}) -- AZ: Seems odd
+    ,"","","")
+
+withInitialCurrentDirFilePath :: FilePath
+withInitialCurrentDirFilePath | isWindows = ".\\Functional.hs"
+                              | otherwise = "./Functional.hs"
+
+noNormalizedUriTxt :: Text
+noNormalizedUriTxt | isWindows = "file:///c:/Users/./myself/././example.hs"
+                   | otherwise = "file:///home/./myself/././example.hs"
+
+noNormalizedUri :: Uri
+noNormalizedUri = Uri noNormalizedUriTxt
+
+uriFilePathSpec :: Spec
+uriFilePathSpec = do
+  it "converts a URI to a file path" $ do
+    let theFilePath = uriToFilePath testUri
+    theFilePath `shouldBe` Just testFilePath
+
+  it "converts a file path to a URI" $ do
+    let theUri = filePathToUri testFilePath
+    theUri `shouldBe` testUri
+
+  it "removes unnecesary current directory paths" $ do
+    let theUri = filePathToUri withCurrentDirFilePath
+    theUri `shouldBe` testUri
+
+  when isWindows $
+    it "make the drive letter upper case when converting a Windows file path to a URI" $ do
+      let theUri = filePathToUri withLowerCaseDriveLetterFilePath
+      theUri `shouldBe` testUri
+
+  it "converts a file path to a URI and back" $ property $ forAll genFilePath $ \fp -> do
+      let uri = filePathToUri fp
+      uriToFilePath uri `shouldBe` Just (normalise fp)
+
+  it "converts a relative file path to a URI and back" $ do
+    let uri = filePathToUri relativeFilePath
+    uri `shouldBe` fromRelativefilePathUri
+    let back = uriToFilePath uri
+    back `shouldBe` Just relativeFilePath
+
+  it "converts a file path with initial current dir to a URI and back" $ do
+    let uri = filePathToUri withInitialCurrentDirFilePath
+    uri `shouldBe` (Uri (pack withInitialCurrentDirUriStr))
+    let Just (URI scheme' auth' path' query' frag') =  parseURI withInitialCurrentDirUriStr
+    (scheme',auth',path',query',frag') `shouldBe` withInitialCurrentDirUriParts
+    Just "Functional.hs" `shouldBe` uriToFilePath uri
+
 uriNormalizeSpec :: Spec
 uriNormalizeSpec = do
-  it "ignores differences in percent-encoding" $ property $ \uri -> do
+
+  it "ignores differences in percent-encoding" $ property $ \uri ->
     toNormalizedUri (Uri $ pack $ escapeURIString isUnescapedInURI uri) `shouldBe`
         toNormalizedUri (Uri $ pack $ escapeURIString (const False) uri)
 
+  it "ignores differences in percent-encoding (examples)" $ do
+    toNormalizedUri (Uri $ pack "http://server/path%C3%B1?param=%C3%B1") `shouldBe`
+        toNormalizedUri (Uri $ pack "http://server/path%c3%b1?param=%c3%b1")
+    toNormalizedUri (Uri $ pack "file:///path%2A") `shouldBe`
+        toNormalizedUri (Uri $ pack "file:///path%2a")
+
+  it "normalizes uri file path when converting from uri to normalized uri" $ do
+    let (NormalizedUri _ uri) = toNormalizedUri noNormalizedUri
+    let (Uri nuri) = testUri
+    uri `shouldBe` nuri
+
+  it "converts a file path with reserved uri chars to a normalized URI and back" $ do
+    let start = if isWindows then "C:\\" else "/"
+    let fp = start ++ "path;part#fragmen?param=val"
+    let nuri = toNormalizedUri (filePathToUri fp)
+    uriToFilePath (fromNormalizedUri nuri) `shouldBe` Just fp
+
+  it "converts a file path with substrings that looks like uri escaped chars and back" $ do
+    let start = if isWindows then "C:\\" else "/"
+    let fp = start ++ "ca%C3%B1a"
+    let nuri = toNormalizedUri (filePathToUri fp)
+    uriToFilePath (fromNormalizedUri nuri) `shouldBe` Just fp
+
+  it "converts a file path to a normalized URI and back" $ property $ forAll genFilePath $ \fp -> do
+    let nuri = toNormalizedUri (filePathToUri fp)
+    case uriToFilePath (fromNormalizedUri nuri) of
+      Just nfp -> nfp `shouldBe` (normalise fp)
+      Nothing -> return () -- Some unicode paths creates invalid uris, ignoring for now
+
+genFilePath :: Gen FilePath
+genFilePath | isWindows = genWindowsFilePath
+            | otherwise = genPosixFilePath
+
 genWindowsFilePath :: Gen FilePath
 genWindowsFilePath = do
-    segments <- listOf pathSegment
+    segments <- listOf1 pathSegment
     pathSep <- elements ['/', '\\']
     driveLetter <- elements ["C:", "c:"]
     pure (driveLetter <> [pathSep] <> intercalate [pathSep] segments)
@@ -139,7 +247,7 @@
 
 genPosixFilePath :: Gen FilePath
 genPosixFilePath = do
-    segments <- listOf pathSegment
+    segments <- listOf1 pathSegment
     pure ("/" <> intercalate "/" segments)
   where pathSegment = listOf1 (genValidUnicodeChar `suchThat` (`notElem` ['/']))
 
@@ -154,3 +262,32 @@
   where
     isSurrogate c = generalCategory c == Surrogate
 #endif
+
+normalizedFilePathSpec :: Spec
+normalizedFilePathSpec = do
+  it "makes file path normalized" $ property $ forAll genFilePath $ \fp -> do
+    let nfp = toNormalizedFilePath fp
+    fromNormalizedFilePath nfp `shouldBe` (normalise fp)
+
+  it "converts to a normalized uri and back" $ property $ forAll genFilePath $ \fp -> do
+    let nuri = normalizedFilePathToUri (toNormalizedFilePath fp)
+    case uriToNormalizedFilePath nuri of
+      Just nfp -> fromNormalizedFilePath nfp `shouldBe` (normalise fp)
+      Nothing -> return () -- Some unicode paths creates invalid uris, ignoring for now
+
+  it "converts a file path with reserved uri chars to a normalized URI and back" $ do
+    let start = if isWindows then "C:\\" else "/"
+    let fp = start ++ "path;part#fragmen?param=val"
+    let nuri = normalizedFilePathToUri (toNormalizedFilePath fp)
+    fmap fromNormalizedFilePath (uriToNormalizedFilePath nuri) `shouldBe` Just fp
+
+  it "converts a file path with substrings that looks like uri escaped chars and back" $ do
+    let start = if isWindows then "C:\\" else "/"
+    let fp = start ++ "ca%C3%B1a"
+    let nuri = normalizedFilePathToUri (toNormalizedFilePath fp)
+    fmap fromNormalizedFilePath (uriToNormalizedFilePath nuri) `shouldBe` Just fp
+
+  it "creates the same NormalizedUri than the older implementation" $ property $ forAll genFilePath $ \fp -> do
+    let nuri = normalizedFilePathToUri (toNormalizedFilePath fp)
+    let oldNuri = toNormalizedUri (filePathToUri fp)
+    nuri `shouldBe` oldNuri
diff --git a/test/VspSpec.hs b/test/VspSpec.hs
--- a/test/VspSpec.hs
+++ b/test/VspSpec.hs
@@ -322,3 +322,6 @@
 
       pp14 <- getCompletionPrefix (J.Position 1 14) (vfsFromText orig)
       pp14 `shouldBe` Just (PosPrefixInfo "import Data.List" "Data" "Li" (J.Position 1 14))
+
+      pp00 <- getCompletionPrefix (J.Position 0 0) (vfsFromText orig)
+      pp00 `shouldBe` Just (PosPrefixInfo "{-# ings #-}" "" "" (J.Position 0 0))
