diff --git a/modules/kernels/cpp/default.nix b/modules/kernels/cpp/default.nix index 2a42e4b..9d5a58a 100644 --- a/modules/kernels/cpp/default.nix +++ b/modules/kernels/cpp/default.nix @@ -5,6 +5,7 @@ , clang , xeus-cling , llvmPackages +, system , settings , settingsSchema @@ -23,7 +24,10 @@ let common = callPackage ../common.nix {}; languageServers = lib.optionals settings.lsp.clangd.enable - [(callPackage ./language_server_clangd { inherit kernelName llvmPackages; })]; + [(callPackage ./language_server_clangd { + inherit kernelName llvmPackages system cling; + settings = settings.lsp.clangd; + })]; displaySuffix = { "c++17" = " 17"; diff --git a/modules/kernels/cpp/language_server_clangd/cling-parser.nix b/modules/kernels/cpp/language_server_clangd/cling-parser.nix new file mode 100644 index 0000000..f7def87 --- /dev/null +++ b/modules/kernels/cpp/language_server_clangd/cling-parser.nix @@ -0,0 +1,37 @@ +{ lib +, stdenv +, fetchFromGitHub +, cmake +, cling +, zlib +, ncurses +}: + +stdenv.mkDerivation rec { + pname = "cling-parser"; + version = import ./cnls-version.nix; + + src = fetchFromGitHub { + owner = "codedownio"; + repo = "cpp-notebook-language-server"; + rev = "v${version}"; + hash = "sha256-+Af5Rn03iaV5JcKVr8625YPOjqZx8Pf/4Chv6bqcwJY="; + }; + + sourceRoot = "${src.name}/cling-parser"; + + nativeBuildInputs = [ cmake ]; + + buildInputs = [ cling.unwrapped zlib ncurses ]; + + cmakeFlags = [ + "-DLLVM_CONFIG=${cling.unwrapped}/bin/llvm-config" + ]; + + meta = with lib; { + description = "Minimal Cling parser for cpp-notebook-language-server"; + homepage = "https://github.com/codedownio/cpp-notebook-language-server"; + license = licenses.bsd3; + platforms = platforms.all; + }; +} diff --git a/modules/kernels/cpp/language_server_clangd/cnls-update.sh b/modules/kernels/cpp/language_server_clangd/cnls-update.sh new file mode 100755 index 0000000..d359cdd --- /dev/null +++ b/modules/kernels/cpp/language_server_clangd/cnls-update.sh @@ -0,0 +1,44 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i bash -p nix-prefetch python3 + +VERSION=$(nix eval --raw --expr 'import ./cnls-version.nix' --impure) + +echo "Got version: $VERSION" + +NAME="cpp-notebook-language-server-$VERSION" + +NEW_HASHES=$( + for system in aarch64-linux x86_64-linux x86_64-darwin aarch64-darwin; do + URL="https://github.com/codedownio/cpp-notebook-language-server/releases/download/v${VERSION}/cpp-notebook-language-server-${VERSION}-${system}.tar.gz" + HASH=$(nix-prefetch fetchzip --name "$NAME" --no-stripRoot --url "$URL" 2>/dev/null) + + echo >&2 "$URL -> $HASH" + + echo " \"$system\" = fetchzip {" + echo " name = \"$NAME\";" + echo " stripRoot = false;" + echo " url = \"$URL\";" + echo " hash = \"$HASH\";" + echo " };" + done +) + +py_script=$(cat <>= liftIO . newQSem . getParallelism diff --git a/tests/app/Spec/Tests/Cpp.hs b/tests/app/Spec/Tests/Cpp.hs index ff32492..3d8aeb1 100644 --- a/tests/app/Spec/Tests/Cpp.hs +++ b/tests/app/Spec/Tests/Cpp.hs @@ -15,30 +15,29 @@ import TestLib.NixTypes import TestLib.TestSearchers import TestLib.Types +import qualified Spec.Tests.Cpp.Completion as Completion +import qualified Spec.Tests.Cpp.Hovers as Hovers -tests :: LanguageSpec -tests = describe "C++" $ parallel $ do - testKernelSearchersBuild "cpp" - testHasExpectedFields "cpp" - -- tests' "cpp98" - -- tests' "c++11" - -- tests' "c++14" - tests' "c++17" - tests' "c++20" - tests' "c++23" - tests' "c++2c" +tests :: LanguageSpec +tests = do + describe "C++" $ do + testKernelSearchersBuild "cpp" + testHasExpectedFields "cpp" - testsWithLsp "c++23" + parallel $ do + tests' "c++17" + tests' "c++20" + tests' "c++23" + tests' "c++2c" tests' :: Text -> LanguageSpec -tests' flavor = describe [i|C++ (#{flavor})|] $ introduceNixEnvironment [kernelSpec flavor] [] "C++" $ introduceJupyterRunner $ do - testKernelStdout "cpp" [__i|\#include - using namespace std; - cout << "hi" << endl;|] "hi\n" +tests' flavor = describe [i|C++ (#{flavor})|] $ introduceNixEnvironment [kernelSpecWithLsp flavor] [] "C++ Nix env" $ introduceJupyterRunner $ do + describe "Kernel tests" $ do + testKernelStdout "cpp" [__i|\#include + using namespace std; + cout << "hi" << endl;|] "hi\n" -testsWithLsp :: Text -> LanguageSpec -testsWithLsp flavor = describe [i|C++ (#{flavor}) with LSP|] $ introduceNixEnvironment [kernelSpecWithLsp flavor] [] "C++" $ do describe "LSP" $ do testDiagnostics'' "simple" lsName "test.cpp" LanguageKind_CPP [__i|int main() { @@ -49,23 +48,26 @@ testsWithLsp flavor = describe [i|C++ (#{flavor}) with LSP|] $ introduceNixEnvir info [i|Got ranges: #{getDiagnosticRanges' diags}|] getDiagnosticRanges' diags `shouldBe` [(Range (Position 1 2) (Position 1 20), Just (InR "undeclared_var_use"), "Use of undeclared identifier 'undefined_function'")] + Completion.tests + + Hovers.tests + lsName :: Text lsName = "clangd" -kernelSpec :: Text -> NixKernelSpec -kernelSpec flavor = kernelSpec' [[i|flavor = "#{flavor}"|]] - kernelSpecWithLsp :: Text -> NixKernelSpec kernelSpecWithLsp flavor = kernelSpec' [ [i|flavor = "#{flavor}"|] - , "lsp.clangd.enable = true" - ] + , "lsp.clangd.enable = true" + , "lsp.clangd.debug = true" + -- , "lsp.clangd.super-debug = true" + ] kernelSpec' :: [Text] -> NixKernelSpec kernelSpec' extraConfig = NixKernelSpec { nixKernelName = "cpp" , nixKernelChannel = "codedown" - , nixKernelDisplayName = Just "CPP" + , nixKernelDisplayName = Just "C++" , nixKernelPackages = [] , nixKernelMeta = Nothing , nixKernelIcon = Nothing diff --git a/tests/app/Spec/Tests/Cpp/Common.hs b/tests/app/Spec/Tests/Cpp/Common.hs new file mode 100644 index 0000000..8e28aac --- /dev/null +++ b/tests/app/Spec/Tests/Cpp/Common.hs @@ -0,0 +1,7 @@ +module Spec.Tests.Cpp.Common where + +import Data.Text + + +lsName :: Text +lsName = "clangd" diff --git a/tests/app/Spec/Tests/Cpp/Completion.hs b/tests/app/Spec/Tests/Cpp/Completion.hs new file mode 100644 index 0000000..bef332c --- /dev/null +++ b/tests/app/Spec/Tests/Cpp/Completion.hs @@ -0,0 +1,56 @@ +module Spec.Tests.Cpp.Completion (tests) where + +import Control.Monad +import Control.Monad.IO.Unlift +import qualified Data.List as L +import Data.Maybe +import Data.String.Interpolate +import Data.Text (Text) +import qualified Data.Text as T +import Language.LSP.Protocol.Types +import Language.LSP.Test +import qualified Language.LSP.Test.Helpers as Helpers +import Spec.Tests.Cpp.Common +import Test.Sandwich as Sandwich +import Test.Sandwich.Waits (waitUntil) +import TestLib.LSP +import TestLib.Types + + +tests :: (LspContext context m, HasNixEnvironment context) => SpecFree context m () +tests = describe "Completions" $ do + forM_ ["main.ipynb", "test.cpp"] $ \doc -> describe (T.unpack doc) $ do + it [i|provides std:: completions|] $ doSession' doc lsName stdCompletionCode $ \(Helpers.LspSessionInfo {..}) -> do + ident <- openDoc lspSessionInfoFileName LanguageKind_CPP + + waitUntil 60 $ do + completions <- getCompletions ident (Position 2 5) + info [i|Got completions: #{completions}|] + let insertTexts = mapMaybe _insertText completions + insertTexts `listShouldContain` "iostream" + insertTexts `listShouldContain` "vector" + + it [i|provides local variable completions|] $ doSession' doc lsName localVarCode $ \(Helpers.LspSessionInfo {..}) -> do + ident <- openDoc lspSessionInfoFileName LanguageKind_CPP + + waitUntil 60 $ do + completions <- getCompletions ident (Position 2 2) + info [i|Got completions: #{completions}|] + let insertTexts = mapMaybe _insertText completions + insertTexts `listShouldContain` "myVariable" + insertTexts `listShouldContain` "myDouble" + +stdCompletionCode :: Text +stdCompletionCode = [__i|\#include + \#include + std::|] + +localVarCode :: Text +localVarCode = [__i|int myVariable = 42; + double myDouble = 3.14; + my|] + +listShouldContain :: (MonadIO m, Eq a, Show a) => [a] -> a -> m () +listShouldContain haystack needle = case L.elem needle haystack of + True -> return () + False -> expectationFailure [i|Expected list to contain #{show needle}, but had: #{show haystack}|] diff --git a/tests/app/Spec/Tests/Cpp/Hovers.hs b/tests/app/Spec/Tests/Cpp/Hovers.hs new file mode 100644 index 0000000..0f23254 --- /dev/null +++ b/tests/app/Spec/Tests/Cpp/Hovers.hs @@ -0,0 +1,54 @@ +module Spec.Tests.Cpp.Hovers (tests) where + +import Control.Monad +import Control.Monad.IO.Unlift +import Data.String.Interpolate +import Data.Text (Text) +import qualified Data.Text as T +import Language.LSP.Protocol.Types +import Language.LSP.Test +import qualified Language.LSP.Test.Helpers as Helpers +import Spec.Tests.Cpp.Common +import Test.Sandwich as Sandwich +import Test.Sandwich.Waits (waitUntil) +import TestLib.LSP +import TestLib.Types +import UnliftIO.Exception + + +tests :: (LspContext context m, HasNixEnvironment context) => SpecFree context m () +tests = describe "Hovers" $ do + forM_ ["main.ipynb", "test.cpp"] $ \doc -> describe (T.unpack doc) $ do + -- it [i|hovers std::cout (#{doc})|] $ doSession' doc lsName coutCode $ \(Helpers.LspSessionInfo {..}) -> do + -- ident <- openDoc lspSessionInfoFileName LanguageKind_CPP + + -- waitUntil 60 $ do + -- hover <- getHoverOrException ident (Position 1 6) + -- allHoverText hover `textShouldContain` [i|std::ostream|] + + -- it [i|hovers variable declaration (#{doc})|] $ doSession' doc lsName varDeclCode $ \(Helpers.LspSessionInfo {..}) -> do + -- ident <- openDoc lspSessionInfoFileName LanguageKind_CPP + + -- waitUntil 60 $ do + -- hover <- getHoverOrException ident (Position 0 4) + -- allHoverText hover `textShouldContain` [i|int|] + + it [i|hovers function call (#{doc})|] $ doSession' doc lsName sqrtCode $ \(Helpers.LspSessionInfo {..}) -> do + ident <- openDoc lspSessionInfoFileName LanguageKind_CPP + + waitUntil 60 $ do + hover <- getHoverOrException ident (Position 1 16) + allHoverText hover `textShouldContain` [i|sqrt|] + + +coutCode :: Text +coutCode = [__i|\#include + std::cout << "hello" << std::endl;|] + +varDeclCode :: Text +varDeclCode = [__i|int x = 42; + float y = 3.14;|] + +sqrtCode :: Text +sqrtCode = [__i|\#include + double result = sqrt(16.0);|] diff --git a/tests/src/TestLib/NixEnvironmentContext.hs b/tests/src/TestLib/NixEnvironmentContext.hs index 3a082fd..5bcfdda 100644 --- a/tests/src/TestLib/NixEnvironmentContext.hs +++ b/tests/src/TestLib/NixEnvironmentContext.hs @@ -35,7 +35,7 @@ introduceNixEnvironment :: ( -> Text -> SpecFree (LabelValue "nixEnvironment" FilePath :> context) m () -> SpecFree context m () -introduceNixEnvironment kernels otherConfig label = introduceWith' (defaultNodeOptions {nodeOptionsVisibilityThreshold = 50}) [i|#{label}|] nixEnvironment $ \action -> do +introduceNixEnvironment kernels otherConfig label = introduceWith' (defaultNodeOptions {nodeOptionsVisibilityThreshold = 60}) [i|#{label}|] nixEnvironment $ \action -> do rootDir <- findFirstParentMatching (\x -> doesPathExist (x ".git")) metadata :: A.Object <- bracket (openFile "/dev/null" WriteMode) hClose $ \devNullHandle -> do diff --git a/tests/tests.cabal b/tests/tests.cabal index 6a155a3..d00afca 100644 --- a/tests/tests.cabal +++ b/tests/tests.cabal @@ -91,6 +91,9 @@ executable tests Spec.Tests.Clojure Spec.Tests.Coq Spec.Tests.Cpp + Spec.Tests.Cpp.Common + Spec.Tests.Cpp.Completion + Spec.Tests.Cpp.Hovers Spec.Tests.Exporters Spec.Tests.Go Spec.Tests.Haskell