diff --git a/.github/workflows/stack.yml b/.github/workflows/stack.yml index 799cf51..d69e776 100644 --- a/.github/workflows/stack.yml +++ b/.github/workflows/stack.yml @@ -96,5 +96,5 @@ jobs: if [ $RUNNER_OS = "Windows" ]; then stack test else - nix shell nixpkgs#agda nixpkgs#neovim -c stack test + nix shell -c stack test fi diff --git a/HACKING.md b/HACKING.md new file mode 100644 index 0000000..ba588ae --- /dev/null +++ b/HACKING.md @@ -0,0 +1,18 @@ +# Working on cornelis + +The project can be built and tested with both `stack` and `cabal`. In order to +successfully run the test suite, both `agda` and `nvim` need to be on `$PATH`. + +## Development using Nix + +Nix integration is provided in the form of a Nix Flake. To build the project, +simply run `nix build`. For interactive development, use the provided shell: + +```sh +# Launch a shell with all development dependencies installed +# (cabal, system libraries, etc.): +nix develop + +# Run tests from a shell that includes a Neovim binary: +nix develop --command cabal test +``` diff --git a/cornelis.cabal b/cornelis.cabal index 55c99ec..521721b 100644 --- a/cornelis.cabal +++ b/cornelis.cabal @@ -97,20 +97,18 @@ library TypeSynonymInstances ghc-options: -Wall build-depends: - QuickCheck >=2.14.2 - , aeson >=1.5.6.0 + aeson >=1.5.6.0 , async , base >=4.7 && <5 , bytestring , containers , diff-loc >=0.1.0.0 , directory + , either ==5.* , filepath , fingertree >=0.1.4.2 , generic-lens >=2.1.0.0 - , hspec >=2.7.10 , lens >=4.19.2 - , levenshtein >=0.1.3.0 , megaparsec >=9.0.1 && <10 , mtl , nvim-hs >=2.2.0.3 && <3 @@ -181,8 +179,7 @@ executable cornelis TypeSynonymInstances ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N build-depends: - QuickCheck >=2.14.2 - , aeson >=1.5.6.0 + aeson >=1.5.6.0 , async , base >=4.7 && <5 , bytestring @@ -190,12 +187,11 @@ executable cornelis , cornelis , diff-loc >=0.1.0.0 , directory + , either ==5.* , filepath , fingertree >=0.1.4.2 , generic-lens >=2.1.0.0 - , hspec >=2.7.10 , lens >=4.19.2 - , levenshtein >=0.1.3.0 , megaparsec >=9.0.1 && <10 , mtl , nvim-hs >=2.2.0.3 && <3 @@ -279,6 +275,7 @@ test-suite test , cornelis , diff-loc >=0.1.0.0 , directory + , either ==5.* , filepath , fingertree >=0.1.4.2 , generic-lens >=2.1.0.0 diff --git a/flake.lock b/flake.lock index 5fbc7f0..5349622 100644 --- a/flake.lock +++ b/flake.lock @@ -36,11 +36,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1711231723, - "narHash": "sha256-dARJQ8AJOv6U+sdRePkbcVyVbXJTi1tReCrkkOeusiA=", + "lastModified": 1715867414, + "narHash": "sha256-cu4UEffKkBByyGR6CFs9XP6iSNsKTkq1r66DA5BkYnE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e1d501922fd7351da4200e1275dfcf5faaad1220", + "rev": "bf446f08bff6814b569265bef8374cfdd3d8f0e0", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index b5d24ef..5349186 100644 --- a/flake.nix +++ b/flake.nix @@ -22,11 +22,16 @@ haskell = prev.haskell // { packageOverrides = final.lib.composeExtensions prev.haskell.packageOverrides - (hfinal: hprev: { + (hfinal: hprev: let + inherit (final.haskell.lib.compose) enableSeparateBinOutput addTestToolDepends; + inherit (final.lib) pipe; + in { # Put binaries into separate output "bin" to reduce closure size. # https://nixos.org/manual/nixpkgs/stable/#haskell-packaging-helpers - ${name} = final.haskell.lib.enableSeparateBinOutput - (hfinal.callCabal2nix name ./. { }); + ${name} = pipe (hfinal.callCabal2nix name ./. { }) [ + enableSeparateBinOutput + (addTestToolDepends [ final.agda final.neovim ]) + ]; }); }; @@ -53,7 +58,7 @@ }; agda = pkgs.agda.withPackages (p: [ p.standard-library ]); in - { + rec { packages = { inherit agda; ${name} = pkgs.${name}; @@ -65,6 +70,15 @@ (v: { name = "${name}-${v}"; value = pkgs.haskell.packages.${v}.${name}; }) ghcVersions ); + defaultPackage = packages.default; + app = { + cornelis = inputs.flake-utils.lib.mkApp { + inherit name; drv = packages.default; + }; + default = app.cornelis; + }; + defaultApp = app.default; + devShells.default = pkgs.callPackage ./nix/dev-shells.nix { }; } ); } diff --git a/nix/dev-shells.nix b/nix/dev-shells.nix new file mode 100644 index 0000000..c5b2432 --- /dev/null +++ b/nix/dev-shells.nix @@ -0,0 +1,42 @@ +{ + path, + lib, + makeWrapper, + symlinkJoin, + + haskellPackages, + pkg-config, + stack, + zlib, + icu, +}: +let + stack-nix = symlinkJoin { + name = "stack-nix"; + paths = [ (lib.getBin stack) ]; + nativeBuildInputs = [ makeWrapper ]; + postBuild = '' + wrapProgram $out/bin/stack \ + --append-flags "--nix --no-nix-pure" + ''; + }; + buildInputs = [ + stack-nix + haskellPackages.cabal-install + pkg-config + zlib.dev + zlib.out + icu + ]; +in +haskellPackages.shellFor { + inherit buildInputs; + + packages = p: [ p.cornelis ]; + + # Ensure nix commands do not use the global channel: + NIX_PATH = "nixpkgs=" + path; + + # Ensure system libraries (zlib.so, etc.) are visible to GHC: + LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs; +} diff --git a/package.yaml b/package.yaml index 10b3fab..b29aa12 100644 --- a/package.yaml +++ b/package.yaml @@ -44,10 +44,7 @@ dependencies: - random - megaparsec >= 9.0.1 && < 10 - diff-loc >= 0.1.0.0 - -- hspec >= 2.7.10 -- QuickCheck >= 2.14.2 -- levenshtein >= 0.1.3.0 +- either >= 5 && < 6 default-extensions: - BangPatterns @@ -128,6 +125,9 @@ tests: - hspec - temporary - filepath + - QuickCheck >= 2.14.2 + - levenshtein >= 0.1.3.0 + # build-dependencies: # - hspec-discover diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..3812658 --- /dev/null +++ b/shell.nix @@ -0,0 +1,8 @@ +( import + ( let lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) { src = ./.; } +).shellNix diff --git a/src/Cornelis/Vim.hs b/src/Cornelis/Vim.hs index 508fd46..bf15cb6 100644 --- a/src/Cornelis/Vim.hs +++ b/src/Cornelis/Vim.hs @@ -7,6 +7,7 @@ import Control.Lens ((%~), _head, _last, (&)) import Cornelis.Offsets import Cornelis.Types import Cornelis.Utils (objectToInt, savingCurrentPosition, savingCurrentWindow) +import Data.Either.Combinators (rightToMaybe) import Data.Foldable (toList) import Data.Int import qualified Data.Map as M @@ -139,6 +140,11 @@ getExtmarkIntervalById ns b (Extmark x) = do fmap Just $ traverse (unvimify b) $ Interval (Pos sline scol) $ Pos eline ecol _ -> pure Nothing +getreg :: Text -> Neovim env (Maybe Text) +getreg reg + = (rightToMaybe . fromObject) + <$> (vim_call_function "getreg" $ V.singleton $ toObject reg) + ------------------------------------------------------------------------------ -- | Awful function that does the motion in visual mode and gives you back -- where vim thinks the @'<@ and @'>@ marks are. diff --git a/test/TestSpec.hs b/test/TestSpec.hs index c925f3d..ee0fd62 100644 --- a/test/TestSpec.hs +++ b/test/TestSpec.hs @@ -4,7 +4,6 @@ module TestSpec where import Control.Concurrent (threadDelay) -import Control.Monad (void) import Cornelis.Subscripts (decNextDigitSeq, incNextDigitSeq) import Cornelis.Types import Cornelis.Types.Agda (Rewrite (..)) @@ -25,9 +24,12 @@ broken :: String -> SpecWith a -> SpecWith a broken = before_ . pendingWith spec :: Spec -spec = focus $ do +spec = focus $ parallel $ do let timeout = Seconds 60 + executableSpec "agda" + executableSpec "nvim" + it "should load read-only file" $ do withVim timeout $ \w b -> do env <- cornelisInit @@ -41,12 +43,12 @@ spec = focus $ do goto w 11 8 refine - broken "Times out in CI for unknown reasons" $ diffSpec "should support helper functions" timeout "test/Hello.agda" - [ Swap "" "help_me : Unit"] $ \w _ -> do + vimSpec "should support helper functions" timeout "test/Hello.agda" $ \w _ -> do goto w 11 8 helperFunc Normalised "help_me" liftIO $ threadDelay 5e5 - void $ vim_command "normal! G\"\"p" + reg <- getreg "\"" + liftIO $ reg `shouldBe` Just "help_me : Unit" diffSpec "should case split (unicode lambda)" timeout "test/Hello.agda" [ Add "slap = λ { true → {! !}" diff --git a/test/Utils.hs b/test/Utils.hs index f942c39..0cc0082 100644 --- a/test/Utils.hs +++ b/test/Utils.hs @@ -19,9 +19,11 @@ import Neovim import Neovim.API.Text import Neovim.Test import Plugin +import System.Exit (ExitCode(..)) import System.FilePath (takeBaseName) import System.IO (hFlush, hPutStr) import System.IO.Temp (withSystemTempFile) +import System.Process (rawSystem) import Test.Hspec hiding (after, before) @@ -64,10 +66,12 @@ intervention b d m = do d' <- differing b m liftIO $ d' `shouldBe` d +withNeovimEmbedded :: Seconds -> Neovim () a -> IO () +withNeovimEmbedded secs = runInEmbeddedNeovim' def{cancelAfter = secs} + withVim :: Seconds -> (Window -> Buffer -> Neovim () ()) -> IO () withVim secs m = do - let withNeovimEmbedded f = testWithEmbeddedNeovim f secs () - withNeovimEmbedded Nothing $ do + withNeovimEmbedded secs $ do b <- nvim_create_buf False False w <- vim_get_current_window nvim_win_set_buf w b @@ -92,16 +96,15 @@ vimSpec -> (Window -> Buffer -> Neovim CornelisEnv ()) -> Spec vimSpec name secs fp m = do - let withNeovimEmbedded f = testWithEmbeddedNeovim f secs () it name $ do withSystemTempFile "test.agda" $ \fp' h -> do hPutStr h $ "module " <> takeBaseName fp' <> " where\n" hPutStr h . unlines . tail . lines =<< readFile fp hFlush h - withNeovimEmbedded Nothing $ do + withNeovimEmbedded secs $ do env <- cornelisInit withLocalEnv env $ do - vim_command $ "edit " <> T.pack fp' + nvim_command $ "noswap edit " <> T.pack fp' liftIO $ threadDelay 1e6 w <- vim_get_current_window b <- nvim_win_get_buf w @@ -109,6 +112,13 @@ vimSpec name secs fp m = do liftIO $ threadDelay 5e6 m w b +-- | Assert that 'bin' is executable. +executableSpec :: String -> Spec +executableSpec bin = describe bin $ do + it "is executable" $ do + exit_code <- rawSystem bin ["--version"] + exit_code `shouldBe` ExitSuccess + goto :: Window -> Int -> Int -> Neovim env () goto w row col = setWindowCursor w $ Pos (toOneIndexed row) (toOneIndexed col)