diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,54 @@
+# Fuzzily
+
+Fuzzy string search library in Haskell.
+Uses `TextualMonoid` from
+[monoid-subclasses](https://hackage.haskell.org/package/monoid-subclasses)
+to be able to run on different types of strings.
+
+This is a fork of Joomy Korkut's [fuzzy](https://github.com/joom/fuzzy),
+which itselft was a port of the JavaScript library
+[mattyork/fuzzy](https://github.com/mattyork/fuzzy).
+
+It was initially forked to be used in [TaskLite](https://tasklite.org/)'s
+`find` sub-command.
+
+
+## Usage
+
+```haskell
+> import Text.Fuzzily
+
+> match "fnt" "infinite" ("", "") id HandleCase
+Just (Fuzzily
+        { original = "infinite"
+        , rendered = "infinite"
+        , score = 3
+        })
+
+> match "hsk" ("Haskell",1995) ("<", ">") fst IgnoreCase
+Just (Fuzzily
+        { original = ("Haskell", 1995)
+        , rendered = "<h>a<s><k>ell"
+        , score = 5
+        })
+
+> langs = [("Standard ML", 1990), ("OCaml", 1996), ("Scala", 2003)]
+> filter "ML" langs ("<", ">") fst IgnoreCase
+[ Fuzzily
+    { original = ("Standard ML", 1990)
+    , rendered = "standard <m><l>"
+    , score = 4
+    }
+, Fuzzily
+    { original = ("OCaml", 1996)
+    , rendered = "oca<m><l>"
+    , score = 4
+    }
+]
+
+> simpleFilter "vm" ["vim", "emacs", "virtual machine"]
+["vim","virtual machine"]
+
+> test "brd" "bread"
+True
+```
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+import Distribution.Simple
+
+
+main = defaultMain
diff --git a/fuzzily.cabal b/fuzzily.cabal
new file mode 100644
--- /dev/null
+++ b/fuzzily.cabal
@@ -0,0 +1,62 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.36.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           fuzzily
+version:        0.1.0.0
+synopsis:       Filters a list based on a fuzzy string search
+description:    Fuzzily is a library that filters a list based on a fuzzy string search.
+                Uses 'TextualMonoid' to be able to run on different types of strings.
+category:       Text, Fuzzy, Search, Find, Filter
+homepage:       https://github.com/ad-si/Fuzzily
+bug-reports:    https://github.com/ad-si/Fuzzily/issues
+author:         Adrian Sieber
+maintainer:     mail@adriansieber.com
+copyright:      Adrian Sieber
+license:        ISC
+license-file:   license.txt
+build-type:     Simple
+extra-source-files:
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/ad-si/Fuzzily
+
+library
+  exposed-modules:
+      Text.Fuzzily
+  other-modules:
+      Paths_fuzzily
+  hs-source-dirs:
+      src
+  default-extensions:
+      ImportQualifiedPost
+      NoImplicitPrelude
+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -fno-warn-orphans
+  build-depends:
+      base >=4.18.2 && <5
+    , monoid-subclasses >=1.2.5 && <1.3
+    , protolude >=0.3.4 && <0.4
+  default-language: Haskell2010
+
+test-suite fuzzily-test
+  type: exitcode-stdio-1.0
+  main-is: tests.hs
+  other-modules:
+      Paths_fuzzily
+  hs-source-dirs:
+      tests
+  default-extensions:
+      ImportQualifiedPost
+      NoImplicitPrelude
+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -fno-warn-orphans
+  build-depends:
+      HUnit
+    , base
+    , fuzzily
+    , monoid-subclasses >=1.2.5 && <1.3
+    , protolude >=0.3.4 && <0.4
+  default-language: Haskell2010
diff --git a/license.txt b/license.txt
new file mode 100644
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2020 Adrian Sieber
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/src/Text/Fuzzily.hs b/src/Text/Fuzzily.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Fuzzily.hs
@@ -0,0 +1,202 @@
+{-|
+Fuzzy string search in Haskell.
+Uses 'TextualMonoid' to be able to run on different types of strings.
+-}
+module Text.Fuzzily where
+
+import Protolude as P (
+  Bool (True),
+  Down (Down),
+  Eq ((==)),
+  Int,
+  Maybe (..),
+  Monoid (mempty),
+  Num ((*), (+)),
+  Semigroup ((<>)),
+  Show,
+  const,
+  identity,
+  isJust,
+  map,
+  mapMaybe,
+  not,
+  sortOn,
+  toLower,
+  (.),
+ )
+
+import Data.Monoid.Textual qualified as T
+
+
+{-|
+Included in the return type of `match` and `filter`.
+Contains the original value given, the rendered string
+and the matching score.
+-}
+data Fuzzy val prettyText = Fuzzy
+  { original :: val
+  , rendered :: prettyText
+  , score :: Int
+  }
+  deriving (Show, Eq)
+
+
+data CaseSensitivity
+  = IgnoreCase
+  | HandleCase
+  deriving (Show, Eq)
+
+
+null :: (T.TextualMonoid s) => s -> Bool
+null =
+  not . T.any (const True)
+
+
+{-|
+Returns the rendered output and the
+matching score for a pattern and a text.
+Two examples are given below:
+
+>>> match HandleCase ("", "") identity "fnt" "infinite"
+Just (Fuzzy
+  { original = "infinite"
+  , rendered = "infinite"
+  , score = 3
+  })
+
+>>> match IgnoreCase ("<", ">") fst "hsk" ("Haskell", 1995)
+Just (Fuzzy
+  { original = ("Haskell", 1995)
+  , rendered = "<h>a<s><k>ell"
+  , score = 5
+  })
+-}
+match
+  :: (T.TextualMonoid text)
+  => CaseSensitivity
+  -- ^ Handle or ignore case of search text
+  -> (text, text)
+  -- ^ Text to add before and after each match
+  -> (value -> text)
+  -- ^ Function to extract the text from the container
+  -> text
+  -- ^ Pattern
+  -> value
+  -- ^ Value containing the text to search in
+  -> Maybe (Fuzzy value text)
+  -- ^ Original value, rendered string, and score
+match caseSensitivity (pre, post) extractFunc pattern value =
+  let
+    searchText = extractFunc value
+    (searchTextNorm, patternNorm) =
+      let mapToLower = T.map toLower
+      in  if caseSensitivity == HandleCase
+            then (searchText, pattern)
+            else (mapToLower searchText, pattern)
+
+    (totalScore, _, result, patternFromFold) =
+      T.foldl_'
+        ( \(tot, cur, res, pat) c ->
+            case T.splitCharacterPrefix pat of
+              Nothing ->
+                ( tot
+                , 0
+                , res <> T.singleton c
+                , pat
+                )
+              Just (x, xs) ->
+                if x == c
+                  then
+                    let cur' = cur * 2 + 1
+                    in  ( tot + cur'
+                        , cur'
+                        , res <> pre <> T.singleton c <> post
+                        , xs
+                        )
+                  else
+                    ( tot
+                    , 0
+                    , res <> T.singleton c
+                    , pat
+                    )
+        )
+        (0, 0, mempty, patternNorm)
+        searchTextNorm
+  in
+    if null patternFromFold
+      then Just (Fuzzy value result totalScore)
+      else Nothing
+
+
+{-|
+The function to filter a list of values
+by fuzzy search on the text extracted from them.
+
+>>> langs = [("Standard ML", 1990), ("OCaml", 1996), ("Scala", 2003)]
+>>> filter "ML" langs ("<", ">") fst IgnoreCase
+[ Fuzzy
+  { original = ("Standard ML", 1990)
+  , rendered = "standard <m><l>"
+  , score = 4}
+, Fuzzy
+  { original = ("OCaml", 1996)
+  , rendered = "oca<m><l>"
+  , score = 4
+  }
+]
+-}
+filter
+  :: (T.TextualMonoid text)
+  => CaseSensitivity
+  -- ^ Handle or ignore case of search text
+  -> (text, text)
+  -- ^ Text to add before and after each match
+  -> (value -> text)
+  -- ^ Function to extract the text from the container
+  -> text
+  -- ^ Pattern
+  -> [value]
+  -- ^ List of values containing the text to search in
+  -> [Fuzzy value text]
+  -- ^ List of results, sorted, highest score first
+filter caseSen (pre, post) extractFunc pattern texts =
+  sortOn
+    (Down . score)
+    ( mapMaybe
+        (match caseSen (pre, post) extractFunc pattern)
+        texts
+    )
+
+
+{-|
+Return all elements of the list that have a fuzzy
+match against the pattern. Runs with default settings where
+nothing is added around the matches, as case insensitive.
+
+>>> simpleFilter "vm" ["vim", "emacs", "virtual machine"]
+["vim","virtual machine"]
+-}
+simpleFilter
+  :: (T.TextualMonoid text)
+  => text
+  -- ^ Pattern to look for.
+  -> [text]
+  -- ^ List of texts to check.
+  -> [text]
+  -- ^ The ones that match.
+simpleFilter pattern xs =
+  map
+    original
+    (filter IgnoreCase (mempty, mempty) identity pattern xs)
+
+
+{-|
+Returns false if the pattern and the text do not match at all.
+Returns true otherwise.
+
+>>> test "brd" "bread"
+True
+-}
+test :: (T.TextualMonoid text) => text -> text -> Bool
+test pattern text =
+  isJust (match IgnoreCase (mempty, mempty) identity pattern text)
diff --git a/tests/tests.hs b/tests/tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/tests.hs
@@ -0,0 +1,129 @@
+module Main where
+
+import Protolude (
+  Bool (False, True),
+  IO,
+  Maybe (Just, Nothing),
+  Monad (return),
+  Ord ((>)),
+  head,
+  identity,
+  map,
+  ($),
+  (<$>),
+ )
+
+import Test.HUnit (Assertion, Test (..), runTestTT, (@?=))
+import Text.Fuzzily as Fu (
+  CaseSensitivity (HandleCase, IgnoreCase),
+  Fuzzy (original, rendered, score),
+  filter,
+  match,
+  test,
+ )
+
+
+from :: [Assertion] -> Test
+from xs = TestList (map TestCase xs)
+
+
+tests :: Test
+tests =
+  TestList
+    [ TestLabel "test" $
+        TestList
+          [ TestLabel "should return true when fuzzy match" $
+              from
+                [ Fu.test "back" "imaback" @?= True
+                , Fu.test "back" "bakck" @?= True
+                , Fu.test "shig" "osh kosh modkhigow" @?= True
+                , Fu.test "" "osh kosh modkhigow" @?= True
+                ]
+          , TestLabel "should return false when no fuzzy match" $
+              from
+                [ Fu.test "back" "abck" @?= False
+                , Fu.test "okmgk" "osh kosh modkhigow" @?= False
+                ]
+          ]
+    , TestLabel "match" $
+        TestList
+          [ TestLabel
+              "should return a greater score for consecutive matches of pattern"
+              $ from
+                [ (>)
+                    (Fu.score <$> Fu.match IgnoreCase ("", "") identity "abcd" "zabcd")
+                    (Fu.score <$> Fu.match IgnoreCase ("", "") identity "abcd" "azbcd")
+                    @?= True
+                ]
+          , TestLabel
+              "should return the string as is if no pre/post and case sensitive"
+              $ from
+                [ Fu.rendered
+                    <$> Fu.match
+                      HandleCase
+                      ("", "")
+                      identity
+                      "ab"
+                      "ZaZbZ"
+                      @?= Just "ZaZbZ"
+                ]
+          , TestLabel "should return Nothing on no match" $
+              from
+                [ Fu.match
+                    IgnoreCase
+                    ("", "")
+                    identity
+                    "ZEBRA!"
+                    "ZaZbZ"
+                    @?= Nothing
+                ]
+          , TestLabel "should be case sensitive is specified" $
+              from
+                [ Fu.match
+                    HandleCase
+                    ("", "")
+                    identity
+                    "hask"
+                    "Haskell"
+                    @?= Nothing
+                ]
+          , TestLabel "should be wrap pre and post around matches" $
+              from
+                [ Fu.rendered
+                    <$> Fu.match
+                      HandleCase
+                      ("<", ">")
+                      identity
+                      "brd"
+                      "bread"
+                      @?= Just "<b><r>ea<d>"
+                ]
+          ]
+    , TestLabel "filter" $
+        TestList
+          [ TestLabel "should return list untouched when given empty pattern" $
+              from
+                [ map
+                    Fu.original
+                    (Fu.filter HandleCase ("", "") identity "" ["abc", "def"])
+                    @?= ["abc", "def"]
+                ]
+          , TestLabel "should return the highest score first" $
+              from
+                [ (@?=)
+                    (head (Fu.filter HandleCase ("", "") identity "cb" ["cab", "acb"]))
+                    (head (Fu.filter HandleCase ("", "") identity "cb" ["acb"]))
+                ]
+          ]
+    ]
+
+
+runTests :: IO ()
+runTests = do
+  _ <- runTestTT tests
+  return ()
+
+
+-- | For now, main will run our tests.
+main :: IO ()
+main = runTests
