From f21a6a4492bee2fb3748a756f37d26e698a055dc Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:03 -0700 Subject: [PATCH 01/34] Tests: add LSP helpers that check a language server really formats --- tests/src/TestLib/LSP.hs | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/src/TestLib/LSP.hs b/tests/src/TestLib/LSP.hs index c2190f3e..f0465e6b 100644 --- a/tests/src/TestLib/LSP.hs +++ b/tests/src/TestLib/LSP.hs @@ -24,6 +24,11 @@ module TestLib.LSP ( , itHasHoverSatisfying + , itFormatsAs + , itFormatsAs' + , itReformats + , formatsAs + , Helpers.getHoverOrException , Helpers.allHoverText , Helpers.containsAll @@ -49,6 +54,7 @@ import Language.LSP.Test.Helpers (LanguageServerConfig(..), LspContext, LspSessi import qualified Language.LSP.Test.Helpers as Helpers import System.FilePath import Test.Sandwich as Sandwich +import Test.Sandwich.Waits (waitUntil) import TestLib.Types import UnliftIO.Directory import UnliftIO.Exception @@ -156,6 +162,69 @@ itHasHoverSatisfying name filename languageKind code pos cb = it [i|#{name}: #{s Nothing -> expectationFailure [i|Expected a hover.|] Just x -> lift $ cb x +-- | Format a document and check the result, which is the only way to tell a working formatter +-- from a server that advertises documentFormattingProvider and then can't find its formatter. +itFormatsAs :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Text -> FilePath -> LanguageKind -> Text -> Text -> SpecFree ctx m () +itFormatsAs = itFormatsAs' 180 + +-- | 'itFormatsAs' with an explicit timeout, for servers that need to load a project first. +itFormatsAs' :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Double -> Text -> FilePath -> LanguageKind -> Text -> Text -> SpecFree ctx m () +itFormatsAs' timeoutSeconds name filename languageKind code expected = + itFormats' timeoutSeconds name filename languageKind code (`shouldBe` expected) + +-- | Weaker than 'itFormatsAs': only that formatting rewrote the document. For formatters whose +-- exact output we haven't pinned down, this still catches a server that advertises formatting +-- and can't deliver it. +itReformats :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Text -> FilePath -> LanguageKind -> Text -> SpecFree ctx m () +itReformats name filename languageKind code = + itFormats' 180 name filename languageKind code (`shouldNotBe` code) + +itFormats' :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Double -> Text -> FilePath -> LanguageKind -> Text -> (Text -> ExampleT ctx m ()) -> SpecFree ctx m () +itFormats' timeoutSeconds name filename languageKind code cb = + it [i|#{name}: #{show code} (formatting)|] $ + formats' timeoutSeconds name filename languageKind code cb + +-- | The body of 'itFormatsAs', for specs that need to wrap it (e.g. to mark it 'pending'). +formatsAs :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Double -> Text -> FilePath -> LanguageKind -> Text -> Text -> ExampleT ctx m () +formatsAs timeoutSeconds name filename languageKind code expected = + formats' timeoutSeconds name filename languageKind code (`shouldBe` expected) + +formats' :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Double -> Text -> FilePath -> LanguageKind -> Text -> (Text -> ExampleT ctx m ()) -> ExampleT ctx m () +formats' timeoutSeconds name filename languageKind code cb = do + lspConfig <- findLspConfig name + (pathToUse, closure) <- getPathAndNixEnvironmentClosure + + let lspSessionOptions = (defaultLspSessionOptions lspConfig) { + lspSessionOptionsInitialFileName = filename + , lspSessionOptionsInitialLanguageKind = languageKind + , lspSessionOptionsInitialCode = code + , lspSessionOptionsReadOnlyBinds = closure + , lspSessionOptionsPathEnvVar = pathToUse + } + + withLspSession lspSessionOptions $ \_ -> do + ident <- openDoc filename languageKind + -- formatDoc applies the edits to the session's copy of the document, so retrying after + -- the server has warmed up is harmless: a formatted document just comes back unchanged. + waitUntil timeoutSeconds $ do + formatDoc ident formattingOptions + documentContents ident >>= lift . cb + +formattingOptions :: FormattingOptions +formattingOptions = FormattingOptions 2 True Nothing Nothing Nothing + findLspConfig :: ( MonadIO m, MonadLogger m, MonadReader context m, Sandwich.HasLabel context "nixEnvironment" FilePath ) => Text -> m LanguageServerConfig From 718ff1450efcb4a7062daa59f66a5a507f0b322a Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:08 -0700 Subject: [PATCH 02/34] Bash: declare shfmt as the formatter, and test that formatting works --- modules/kernels/bash/default.nix | 1 + modules/kernels/bash/language_server_bash/default.nix | 1 + tests/app/Spec/Tests/Bash.hs | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/modules/kernels/bash/default.nix b/modules/kernels/bash/default.nix index 4fc124c8..b32290e2 100644 --- a/modules/kernels/bash/default.nix +++ b/modules/kernels/bash/default.nix @@ -72,5 +72,6 @@ symlinkJoin { code_mirror_mode = "shell"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/bash/language_server_bash/default.nix b/modules/kernels/bash/language_server_bash/default.nix index 576098f7..f9a115b0 100644 --- a/modules/kernels/bash/language_server_bash/default.nix +++ b/modules/kernels/bash/language_server_bash/default.nix @@ -39,6 +39,7 @@ let passthru = { inherit languageServerName; + formatters = ["shfmt"]; }; in diff --git a/tests/app/Spec/Tests/Bash.hs b/tests/app/Spec/Tests/Bash.hs index 83a1dffd..02e57d27 100644 --- a/tests/app/Spec/Tests/Bash.hs +++ b/tests/app/Spec/Tests/Bash.hs @@ -46,5 +46,9 @@ tests = describe "Bash" $ introduceNixEnvironment [kernelSpec] [] "Bash" $ intro (Range (Position 0 0) (Position 0 3), Just (InR "SC2034")) ] + itFormatsAs "bash-language-server" "test.sh" LanguageKind_ShellScript + "if [ 1 = 1 ]; then\necho hi\n fi\n" + "if [ 1 = 1 ]; then\n echo hi\nfi\n" + main :: IO () main = jupyterMain tests From 1d78f01d6a5bf30a01802c8643d238c8b978f9ff Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:09 -0700 Subject: [PATCH 03/34] C++: declare clang-format as the formatter, and test that formatting works --- modules/kernels/cpp/default.nix | 1 + modules/kernels/cpp/language_server_clangd/default.nix | 2 ++ tests/app/Spec/Tests/Cpp.hs | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/modules/kernels/cpp/default.nix b/modules/kernels/cpp/default.nix index 05ff4260..659521d7 100644 --- a/modules/kernels/cpp/default.nix +++ b/modules/kernels/cpp/default.nix @@ -133,5 +133,6 @@ symlinkJoin { code_mirror_mime_type = "text/x-c++src"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/cpp/language_server_clangd/default.nix b/modules/kernels/cpp/language_server_clangd/default.nix index 6a7523db..0b7ae661 100644 --- a/modules/kernels/cpp/language_server_clangd/default.nix +++ b/modules/kernels/cpp/language_server_clangd/default.nix @@ -32,6 +32,8 @@ let passthru = { inherit languageServerName; + # clangd links libFormat, so clang-format needs nothing extra on our side. + formatters = ["clang-format"]; }; in diff --git a/tests/app/Spec/Tests/Cpp.hs b/tests/app/Spec/Tests/Cpp.hs index 3d8aeb13..2c0f0094 100644 --- a/tests/app/Spec/Tests/Cpp.hs +++ b/tests/app/Spec/Tests/Cpp.hs @@ -52,6 +52,10 @@ tests' flavor = describe [i|C++ (#{flavor})|] $ introduceNixEnvironment [kernelS Hovers.tests + itFormatsAs lsName "test.cpp" LanguageKind_CPP + "int main(){int x=1+2;return x;}\n" + "int main() {\n int x = 1 + 2;\n return x;\n}\n" + lsName :: Text lsName = "clangd" From cb1e2744be69634d3b01027f6f9c753703bd02ba Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:09 -0700 Subject: [PATCH 04/34] Clojure: declare cljfmt as the formatter, and test that formatting works --- modules/kernels/clojure/default.nix | 1 + modules/kernels/clojure/language-server.nix | 2 ++ tests/app/Spec/Tests/Clojure.hs | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/modules/kernels/clojure/default.nix b/modules/kernels/clojure/default.nix index ff5b7a7d..657a7adb 100644 --- a/modules/kernels/clojure/default.nix +++ b/modules/kernels/clojure/default.nix @@ -70,5 +70,6 @@ symlinkJoin { code_mirror_mode = "clojure"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/clojure/language-server.nix b/modules/kernels/clojure/language-server.nix index f9554436..c77cd3de 100644 --- a/modules/kernels/clojure/language-server.nix +++ b/modules/kernels/clojure/language-server.nix @@ -13,6 +13,8 @@ let passthru = { inherit languageServerName; + # cljfmt is built into clojure-lsp. + formatters = ["cljfmt"]; }; in diff --git a/tests/app/Spec/Tests/Clojure.hs b/tests/app/Spec/Tests/Clojure.hs index b65262da..31fefed6 100644 --- a/tests/app/Spec/Tests/Clojure.hs +++ b/tests/app/Spec/Tests/Clojure.hs @@ -38,6 +38,11 @@ tests = describe "Clojure" $ introduceNixEnvironment [kernelSpec] [] "Clojure" $ testDiagnostics "clojure-lsp" "test.clj" LanguageKind_Clojure [__i|(foo 42)|] $ \diagnostics -> do assertDiagnosticRanges diagnostics [(Range (Position 0 1) (Position 0 4), Just (InR "unresolved-symbol"))] + -- cljfmt reindents but deliberately leaves spacing within a line alone. + itFormatsAs "clojure-lsp" "test.clj" LanguageKind_Clojure + "(defn foo [a b]\n(+ a b))\n" + "(defn foo [a b]\n (+ a b))\n" + main :: IO () main = jupyterMain tests From d67c0d56480d72a1ce9a102f1201c0acabd4e1a2 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:15 -0700 Subject: [PATCH 05/34] Go: add a setting to pick between gofmt and gofumpt, both of which gopls has built in --- modules/kernels/go/default.nix | 1 + .../go/language-server-gopls/language-server-gopls.nix | 6 ++++++ modules/kernels/go/module.nix | 6 ++++++ tests/app/Spec/Tests/Go.hs | 8 ++++++++ 4 files changed, 21 insertions(+) diff --git a/modules/kernels/go/default.nix b/modules/kernels/go/default.nix index 0eee70c9..83f3eb31 100644 --- a/modules/kernels/go/default.nix +++ b/modules/kernels/go/default.nix @@ -68,5 +68,6 @@ symlinkJoin { code_mirror_mode = "go"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/go/language-server-gopls/language-server-gopls.nix b/modules/kernels/go/language-server-gopls/language-server-gopls.nix index 4e2880ad..2602e65c 100644 --- a/modules/kernels/go/language-server-gopls/language-server-gopls.nix +++ b/modules/kernels/go/language-server-gopls/language-server-gopls.nix @@ -41,6 +41,7 @@ let passthru = { inherit languageServerName; + formatters = [settings.formatter]; }; in @@ -66,5 +67,10 @@ common.writeTextDirWithMetaAndPassthru gopls.meta passthru "lib/codedown/languag ++ lib.optionals settings.super-debug ["--debug-client-writes" "--debug-client-reads" "--debug-server-writes" "--debug-server-reads"] ; env = {}; + # gopls formats with gofmt by default and has gofumpt vendored in, so switching between + # them is just this flag -- no extra tool in the closure either way. + initialization_options = { + gofumpt = settings.formatter == "gofumpt"; + }; language_id = "go"; }]) diff --git a/modules/kernels/go/module.nix b/modules/kernels/go/module.nix index e270f01e..020924b6 100644 --- a/modules/kernels/go/module.nix +++ b/modules/kernels/go/module.nix @@ -47,6 +47,12 @@ with lib; type = types.bool; default = true; }; + lsp.gopls.formatter = mkOption { + title = "Go formatter"; + description = "Which formatter gopls uses. Both are built into gopls; gofumpt is gofmt plus a set of stricter rules."; + type = types.enum ["gofmt" "gofumpt"]; + default = "gofmt"; + }; lsp.gopls.debug = mkOption { title = "Gopls: enable debug output"; type = types.bool; diff --git a/tests/app/Spec/Tests/Go.hs b/tests/app/Spec/Tests/Go.hs index d5f88c4e..302c6391 100644 --- a/tests/app/Spec/Tests/Go.hs +++ b/tests/app/Spec/Tests/Go.hs @@ -35,6 +35,8 @@ tests = describe "Go" $ do Hovers.tests + itFormatsAs lsName "test.go" LanguageKind_Go badlyFormattedCode formattedCode + lsName :: Text lsName = "gopls" @@ -52,6 +54,12 @@ kernelSpecWithLsp = NixKernelSpec { ] } +badlyFormattedCode :: Text +badlyFormattedCode = "package main\nimport (\"fmt\")\nfunc main() {\nx:=1+2\nfmt.Println(x)\n}\n" + +formattedCode :: Text +formattedCode = "package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tx := 1 + 2\n\tfmt.Println(x)\n}\n" + printUnknownCode :: Text printUnknownCode = [__i|package main import ("fmt") From f5561b048cb84cb927fee53714dc9ec05e312068 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:15 -0700 Subject: [PATCH 06/34] Haskell: add a setting to pick the haskell-language-server formatting provider --- modules/kernels/haskell/default.nix | 1 + .../haskell/language-server-hls/config.nix | 5 ++++- modules/kernels/haskell/module.nix | 6 ++++++ tests/app/Spec/Tests/Haskell.hs | 2 ++ tests/app/Spec/Tests/Haskell/Formatting.hs | 16 ++++++++++++++++ tests/tests.cabal | 1 + 6 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/app/Spec/Tests/Haskell/Formatting.hs diff --git a/modules/kernels/haskell/default.nix b/modules/kernels/haskell/default.nix index cdbb192e..9bdfc093 100644 --- a/modules/kernels/haskell/default.nix +++ b/modules/kernels/haskell/default.nix @@ -99,5 +99,6 @@ symlinkJoin { code_mirror_mode = "haskell"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/haskell/language-server-hls/config.nix b/modules/kernels/haskell/language-server-hls/config.nix index 0a1ca16d..267c1e14 100644 --- a/modules/kernels/haskell/language-server-hls/config.nix +++ b/modules/kernels/haskell/language-server-hls/config.nix @@ -38,6 +38,7 @@ let passthru = { inherit languageServerName; + formatters = lib.optional (settings.formattingProvider != "none") settings.formattingProvider; }; config = { @@ -74,7 +75,9 @@ let # TODO: expose some of these options, as needed # https://haskell-language-server.readthedocs.io/en/latest/configuration.html haskell = { - # formattingProvider = "ormolu"; # floskell, ormolu, fourmolu, stylish-haskell + # ormolu, fourmolu and stylish-haskell are all compiled into the haskell-language-server + # Nixpkgs builds, so picking one costs nothing extra. + formattingProvider = settings.formattingProvider; # maxCompletions = 40; # checkProject = true; # checkParents = "CheckOnSave"; diff --git a/modules/kernels/haskell/module.nix b/modules/kernels/haskell/module.nix index 22865281..7c09950f 100644 --- a/modules/kernels/haskell/module.nix +++ b/modules/kernels/haskell/module.nix @@ -48,6 +48,12 @@ in type = types.bool; default = true; }; + lsp.haskell-language-server.formattingProvider = mkOption { + title = "Haskell formatter"; + description = "Which formatter haskell-language-server uses. All of these are compiled into it, so picking one costs nothing extra."; + type = types.enum ["ormolu" "fourmolu" "stylish-haskell" "none"]; + default = "ormolu"; + }; lsp.haskell-language-server.debug = mkOption { title = "Haskell-language-server: enable debug output"; type = types.bool; diff --git a/tests/app/Spec/Tests/Haskell.hs b/tests/app/Spec/Tests/Haskell.hs index a13373d3..ae294001 100644 --- a/tests/app/Spec/Tests/Haskell.hs +++ b/tests/app/Spec/Tests/Haskell.hs @@ -20,6 +20,7 @@ import TestLib.Types import qualified Spec.Tests.Haskell.CodeActions as CodeActions import qualified Spec.Tests.Haskell.Diagnostics as Diagnostics import qualified Spec.Tests.Haskell.DocumentHighlight as DocumentHighlight +import qualified Spec.Tests.Haskell.Formatting as Formatting import qualified Spec.Tests.Haskell.Hover as Hover import qualified Spec.Tests.Haskell.Statements as Statements import qualified Spec.Tests.Haskell.Symbols as Symbols @@ -72,6 +73,7 @@ haskellCommonTests ghcPackage = do CodeActions.tests ghcPackage Diagnostics.tests ghcPackage lsName DocumentHighlight.tests + Formatting.tests Hover.tests Statements.tests ghcPackage Symbols.tests diff --git a/tests/app/Spec/Tests/Haskell/Formatting.hs b/tests/app/Spec/Tests/Haskell/Formatting.hs new file mode 100644 index 00000000..31a4309c --- /dev/null +++ b/tests/app/Spec/Tests/Haskell/Formatting.hs @@ -0,0 +1,16 @@ +module Spec.Tests.Haskell.Formatting (tests) where + +import Language.LSP.Protocol.Types +import Spec.Tests.Haskell.Common +import Test.Sandwich as Sandwich +import TestLib.LSP +import TestLib.Types + + +-- | ormolu is the default formattingProvider; see +-- kernels.haskell.lsp.haskell-language-server.formattingProvider for the alternatives. +tests :: (LspContext context m, HasNixEnvironment context) => SpecFree context m () +tests = describe "Formatting" $ + itFormatsAs' 300 lsName "Test.hs" LanguageKind_Haskell + "module Main where\nmain :: IO ()\nmain = putStrLn \"hi\"\n" + "module Main where\n\nmain :: IO ()\nmain = putStrLn \"hi\"\n" diff --git a/tests/tests.cabal b/tests/tests.cabal index a8e74a58..fdb27774 100644 --- a/tests/tests.cabal +++ b/tests/tests.cabal @@ -107,6 +107,7 @@ executable tests Spec.Tests.Haskell.Common Spec.Tests.Haskell.Diagnostics Spec.Tests.Haskell.DocumentHighlight + Spec.Tests.Haskell.Formatting Spec.Tests.Haskell.Hover Spec.Tests.Haskell.Info Spec.Tests.Haskell.Statements From 39f7d045652d7b69c1f77640feab18c8178ab59d Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:22 -0700 Subject: [PATCH 07/34] JavaScript and TypeScript: declare tsserver as the formatter, and test that formatting works --- modules/kernels/javascript/default.nix | 1 + .../javascript/language_server_typescript/config.nix | 2 ++ tests/app/Spec/Tests/Javascript.hs | 7 +++++++ tests/app/Spec/Tests/Typescript.hs | 7 +++++++ tests/tests.cabal | 5 +++++ 5 files changed, 22 insertions(+) diff --git a/modules/kernels/javascript/default.nix b/modules/kernels/javascript/default.nix index 41922b41..973a54c4 100644 --- a/modules/kernels/javascript/default.nix +++ b/modules/kernels/javascript/default.nix @@ -118,5 +118,6 @@ symlinkJoin { code_mirror_mode = "javascript"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/javascript/language_server_typescript/config.nix b/modules/kernels/javascript/language_server_typescript/config.nix index 81240c33..b938c764 100644 --- a/modules/kernels/javascript/language_server_typescript/config.nix +++ b/modules/kernels/javascript/language_server_typescript/config.nix @@ -65,6 +65,8 @@ let passthru = { inherit languageServerName workspaceConfig; + # tsserver's own formatter; nothing extra to install. + formatters = ["tsserver"]; }; in diff --git a/tests/app/Spec/Tests/Javascript.hs b/tests/app/Spec/Tests/Javascript.hs index 412c2343..7e4a3fbd 100644 --- a/tests/app/Spec/Tests/Javascript.hs +++ b/tests/app/Spec/Tests/Javascript.hs @@ -4,8 +4,11 @@ module Spec.Tests.Javascript (tests) where import Data.String.Interpolate +import Language.LSP.Protocol.Types +import Spec.Tests.Javascript.Common import Test.Sandwich as Sandwich import TestLib.JupyterRunnerContext +import TestLib.LSP import TestLib.NixEnvironmentContext import TestLib.NixTypes import TestLib.TestSearchers @@ -44,6 +47,10 @@ tests = describe "JavaScript" $ do Hovers.tests + itFormatsAs lsName "test.js" LanguageKind_JavaScript + "const x = 1+2\nconsole.log( x )\n" + "const x = 1 + 2\nconsole.log(x)\n" + kernelSpec :: NixKernelSpec kernelSpec = NixKernelSpec { nixKernelName = "javascript" diff --git a/tests/app/Spec/Tests/Typescript.hs b/tests/app/Spec/Tests/Typescript.hs index 0e160b1a..5c472dc5 100644 --- a/tests/app/Spec/Tests/Typescript.hs +++ b/tests/app/Spec/Tests/Typescript.hs @@ -4,8 +4,11 @@ module Spec.Tests.Typescript (tests) where import Data.String.Interpolate +import Language.LSP.Protocol.Types +import Spec.Tests.Typescript.Common import Test.Sandwich as Sandwich import TestLib.JupyterRunnerContext +import TestLib.LSP import TestLib.NixEnvironmentContext import TestLib.NixTypes import TestLib.TestSearchers @@ -45,6 +48,10 @@ tests = describe "TypeScript" $ do Hovers.tests + itFormatsAs lsName "test.ts" LanguageKind_TypeScript + "const x : number = 1+2\nconsole.log( x )\n" + "const x: number = 1 + 2\nconsole.log(x)\n" + kernelSpec :: NixKernelSpec kernelSpec = NixKernelSpec { nixKernelName = "typescript" diff --git a/tests/tests.cabal b/tests/tests.cabal index fdb27774..4eaf176f 100644 --- a/tests/tests.cabal +++ b/tests/tests.cabal @@ -115,6 +115,7 @@ executable tests Spec.Tests.Javascript Spec.Tests.Javascript.Common Spec.Tests.Javascript.Completion + Spec.Tests.Javascript.Diagnostics Spec.Tests.Javascript.Display Spec.Tests.Javascript.Hovers Spec.Tests.Javascript.Packages @@ -143,6 +144,10 @@ executable tests Spec.Tests.SettingsSchemas Spec.Tests.Shells.Zsh Spec.Tests.Spellchecker + Spec.Tests.Typescript + Spec.Tests.Typescript.Common + Spec.Tests.Typescript.Completion + Spec.Tests.Typescript.Hovers Spec.Tests.Typst Paths_tests hs-source-dirs: From ed62db1e9451f8ba1db5b4f6efac4351695a5b7c Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:23 -0700 Subject: [PATCH 08/34] Julia: declare JuliaFormatter as the formatter, and test that formatting works --- modules/kernels/julia/default.nix | 1 + modules/kernels/julia/language-server-LanguageServer.nix | 4 ++++ tests/app/Spec/Tests/Julia.hs | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/modules/kernels/julia/default.nix b/modules/kernels/julia/default.nix index b39516b3..e9797887 100644 --- a/modules/kernels/julia/default.nix +++ b/modules/kernels/julia/default.nix @@ -110,5 +110,6 @@ symlinkJoin { code_mirror_mode = "julia"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/julia/language-server-LanguageServer.nix b/modules/kernels/julia/language-server-LanguageServer.nix index b1a01342..72d086c6 100644 --- a/modules/kernels/julia/language-server-LanguageServer.nix +++ b/modules/kernels/julia/language-server-LanguageServer.nix @@ -41,6 +41,10 @@ let passthru = { inherit languageServerName; + # LanguageServer.jl formats with JuliaFormatter, which comes in as one of its + # dependencies. Note that it's configured from a .JuliaFormatter.toml in the workspace, + # not from the julia.format.* initialization options below. + formatters = ["JuliaFormatter"]; }; in diff --git a/tests/app/Spec/Tests/Julia.hs b/tests/app/Spec/Tests/Julia.hs index b7e00d1d..aec26c9b 100644 --- a/tests/app/Spec/Tests/Julia.hs +++ b/tests/app/Spec/Tests/Julia.hs @@ -57,6 +57,11 @@ juliaTests juliaPackage = describe [i|Julia (#{juliaPackage})|] $ introduceNixEn ident <- openDoc lspSessionInfoFileName "julia" getHighlights ident (Position 0 1) >>= (`shouldBe` documentHighlightResults) + -- Weaker than the other languages' formatting tests: JuliaFormatter's exact output isn't + -- pinned here, only that LanguageServer.jl actually reformats. + itReformats lsName "test.jl" (LanguageKind_Custom "julia") + "function foo(a,b)\nx=a+b\nreturn x\nend\n" + documentHighlightCode :: Text documentHighlightCode = [__i|foo = "hello" From d12585567f407e996af394486a18f394ded845bc Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:30 -0700 Subject: [PATCH 09/34] Python: add a formatter setting for python-lsp-server, which ships with none --- modules/kernels/python/default.nix | 3 +- .../language_server_pythonlsp/config.nix | 10 ++++- .../language_servers/pylsp_formatter.nix | 38 +++++++++++++++++++ modules/kernels/python/module.nix | 6 +++ tests/app/Spec/Tests/Python.hs | 7 ++++ 5 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 modules/kernels/python/language_servers/pylsp_formatter.nix diff --git a/modules/kernels/python/default.nix b/modules/kernels/python/default.nix index 8ce7e31a..d600cc8a 100644 --- a/modules/kernels/python/default.nix +++ b/modules/kernels/python/default.nix @@ -61,7 +61,7 @@ let ++ lib.optionals settings.lsp.flake8.enable [(callPackage ./language_servers/language_server_flake8/config.nix { inherit pythonWithPackages kernelName attrs; })] ++ lib.optionals settings.lsp.pycodestyle.enable [(callPackage ./language_servers/language_server_pycodestyle/config.nix { inherit pythonWithPackages kernelName attrs; })] ++ lib.optionals settings.lsp.microsoft.enable [(callPackage ./language_servers/language_server_microsoft/config.nix { inherit pythonWithPackages kernelName attrs; })] - ++ lib.optionals (settings.lsp.python-lsp-server.enable && (hasPythonLspServer (pythonWithPackages (ps: [])))) [(callPackage ./language_servers/language_server_pythonlsp/config.nix { inherit pythonWithPackages kernelName attrs; })] + ++ lib.optionals (settings.lsp.python-lsp-server.enable && (hasPythonLspServer (pythonWithPackages (ps: [])))) [(callPackage ./language_servers/language_server_pythonlsp/config.nix { inherit pythonWithPackages kernelName attrs; settings = settings.lsp.python-lsp-server; })] ++ lib.optionals (settings.lsp.python-language-server.enable && (hasPythonLanguageServer (pythonWithPackages (ps: [])))) [(callPackage ./language_servers/language_server_palantir/config.nix { inherit pythonWithPackages kernelName attrs; })] ; @@ -125,5 +125,6 @@ symlinkJoin { code_mirror_mode = "python"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/python/language_servers/language_server_pythonlsp/config.nix b/modules/kernels/python/language_servers/language_server_pythonlsp/config.nix index 160cea22..130c45ac 100644 --- a/modules/kernels/python/language_servers/language_server_pythonlsp/config.nix +++ b/modules/kernels/python/language_servers/language_server_pythonlsp/config.nix @@ -3,6 +3,7 @@ , pythonWithPackages , kernelName , attrs +, settings }: let @@ -22,7 +23,9 @@ let # manylinux1 = callPackage ./manylinux1.nix { inherit python; }; - python = (pythonWithPackages (ps: [ps.python-lsp-server])); + formatter = callPackage ../pylsp_formatter.nix { inherit (settings) formatter; }; + + python = (pythonWithPackages (ps: [ps.python-lsp-server] ++ formatter.packages ps)); # python = (pythonWithPackages (ps: [ps.python-lsp-server])).buildEnv.override { # permitUserSite = false; # makeWrapperArgs = [ @@ -41,6 +44,7 @@ let passthru = { inherit languageServerName; + inherit (formatter) formatters; }; in @@ -59,6 +63,8 @@ common.writeTextDirWithMetaAndPassthru python.pkgs.python-lsp-server.meta passth inherit attrs; type = "stream"; args = ["${python}/bin/python" "-m" "pylsp"]; - initialization_options = import ../pylsp_initialization_options.nix "pylsp"; + initialization_options = lib.recursiveUpdate + (import ../pylsp_initialization_options.nix "pylsp") + { pylsp.plugins = formatter.pluginSettings; }; language_id = "python"; }]) diff --git a/modules/kernels/python/language_servers/pylsp_formatter.nix b/modules/kernels/python/language_servers/pylsp_formatter.nix new file mode 100644 index 00000000..4b4c822b --- /dev/null +++ b/modules/kernels/python/language_servers/pylsp_formatter.nix @@ -0,0 +1,38 @@ +# python-lsp-server always advertises documentFormattingProvider, but it can only actually +# format if one of its formatter plugins is installed -- none of which comes with the base +# package. This turns the kernel's `lsp.python-lsp-server.formatter` setting into the extra +# Python packages to add and the pylsp plugin settings to send. +# +# Every plugin that isn't the chosen one is disabled explicitly: autopep8 registers its hook +# `tryfirst` and both it and yapf default to enabled, so a formatter that happens to be in the +# user's environment would otherwise win over the one they picked. + +{ formatter }: + +let + all = ["autopep8" "yapf" "black" "ruff"]; + + packagesFor = { + none = _ps: []; + autopep8 = ps: [ps.autopep8]; + yapf = ps: [ps.yapf ps.whatthepatch]; + black = ps: [ps.python-lsp-black]; + ruff = ps: [ps.python-lsp-ruff]; + }; + + enabled = name: { + enabled = formatter == name; + } // (if name == "ruff" then { formatEnabled = formatter == "ruff"; } else {}); + +in + +{ + packages = packagesFor.${formatter}; + + pluginSettings = builtins.listToAttrs (map (name: { + inherit name; + value = enabled name; + }) all); + + formatters = if formatter == "none" then [] else [formatter]; +} diff --git a/modules/kernels/python/module.nix b/modules/kernels/python/module.nix index 956c2cfb..319dd002 100644 --- a/modules/kernels/python/module.nix +++ b/modules/kernels/python/module.nix @@ -102,6 +102,12 @@ let type = types.bool; default = false; }; + lsp.python-lsp-server.formatter = mkOption { + title = "Python formatter"; + description = "Which formatter python-lsp-server uses. It ships with none of them, so this also decides what gets added to the environment. autopep8 is the lightest and reuses the pycodestyle settings above; black and ruff reformat more aggressively. Note that Jedi, the default language server, can't format at all -- this only applies when python-lsp-server is enabled."; + type = types.enum ["autopep8" "yapf" "black" "ruff" "none"]; + default = "autopep8"; + }; lsp.python-language-server.enable = mkOption { title = "Enable python-language-server language server"; type = types.bool; diff --git a/tests/app/Spec/Tests/Python.hs b/tests/app/Spec/Tests/Python.hs index 6ccc33ef..d08560e6 100644 --- a/tests/app/Spec/Tests/Python.hs +++ b/tests/app/Spec/Tests/Python.hs @@ -73,6 +73,13 @@ tests' (kernelName, pythonPackage) = introduceNixEnvironment [kernelSpec kernelN |] $ \diagnostics -> do assertDiagnosticRanges diagnostics [] + -- Jedi, the default language server, can't format at all; this is python-lsp-server with + -- its default formatter. The run of blank lines survives because autopep8 reuses the + -- pycodestyle `ignore` list, which has E303 in it (see pylsp_initialization_options.nix). + itFormatsAs "python-lsp-server" "test.py" LanguageKind_Python + "def f( x,y ):\n return x+y\n\n\n\n\nz = f(1,2)\n" + "def f(x, y):\n return x + y\n\n\n\n\nz = f(1, 2)\n" + -- | Nixpkgs carries far less for PyPy than for CPython, so this covers the kernel itself -- rather than the package and language server stack the CPython tests exercise. From d56013debe0d73beab1f83d51d6ae373dc053a8f Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:30 -0700 Subject: [PATCH 10/34] R: declare styler as the formatter --- modules/kernels/r/default.nix | 1 + .../r/language-server-languageserver/default.nix | 2 ++ tests/app/Spec/Tests/R.hs | 12 ++++++++++++ 3 files changed, 15 insertions(+) diff --git a/modules/kernels/r/default.nix b/modules/kernels/r/default.nix index 9f3c152f..5fdaa4db 100644 --- a/modules/kernels/r/default.nix +++ b/modules/kernels/r/default.nix @@ -87,5 +87,6 @@ symlinkJoin { code_mirror_mode = "r"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/r/language-server-languageserver/default.nix b/modules/kernels/r/language-server-languageserver/default.nix index 1d63d720..c25a935e 100644 --- a/modules/kernels/r/language-server-languageserver/default.nix +++ b/modules/kernels/r/language-server-languageserver/default.nix @@ -21,6 +21,8 @@ let passthru = { inherit languageServerName; + # styler is already one of languageserver's R dependencies (see ./languageserver.nix). + formatters = ["styler"]; }; in diff --git a/tests/app/Spec/Tests/R.hs b/tests/app/Spec/Tests/R.hs index cf683b32..41d28386 100644 --- a/tests/app/Spec/Tests/R.hs +++ b/tests/app/Spec/Tests/R.hs @@ -3,8 +3,10 @@ module Spec.Tests.R (tests) where import Data.String.Interpolate +import Language.LSP.Protocol.Types import Test.Sandwich as Sandwich import TestLib.JupyterRunnerContext +import TestLib.LSP import TestLib.NixEnvironmentContext import TestLib.NixTypes import TestLib.TestSearchers @@ -34,6 +36,16 @@ tests = describe "R" $ introduceNixEnvironment [kernelSpec] [] "R" $ introduceJu VariableInspector.tests "R" + it "languageserver formats with styler" $ do + -- The R language server can't start under the hermetic PATH these tests use: loading + -- processx runs system("which uname"), and R's system() goes through popen, which needs + -- a /bin/sh the sandbox doesn't have. styler itself works -- the capability probe, which + -- runs outside the sandbox, formats fine. + _ <- pending + formatsAs 180 "languageserver" "test.R" LanguageKind_R + "f <- function(a,b){\na+b\n}\n" + "f <- function(a, b) {\n a + b\n}\n" + main :: IO () main = jupyterMain tests From 39a2d121a95f5823f54a5caaa4c628fd7b05aa36 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:30 -0700 Subject: [PATCH 11/34] Ruby: turn on Solargraph's rubocop formatting, which it ships with but leaves off --- modules/kernels/ruby/default.nix | 3 ++- modules/kernels/ruby/module.nix | 7 +++++++ modules/kernels/ruby/solargraph.nix | 10 ++++++++-- tests/app/Spec/Tests/Ruby.hs | 7 +++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/modules/kernels/ruby/default.nix b/modules/kernels/ruby/default.nix index 35cab052..0f100a16 100644 --- a/modules/kernels/ruby/default.nix +++ b/modules/kernels/ruby/default.nix @@ -27,7 +27,7 @@ let [] ++ lib.optionals settings.lsp.solargraph.enable [(callPackage ./solargraph.nix { rubyPackages = packageOptions; - rubocopYaml = settings.lsp.solargraph.rubocopYaml; + settings = settings.lsp.solargraph; inherit kernelName; })] ; @@ -81,5 +81,6 @@ symlinkJoin { code_mirror_mode = "ruby"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/ruby/module.nix b/modules/kernels/ruby/module.nix index db67376c..dd3706ae 100644 --- a/modules/kernels/ruby/module.nix +++ b/modules/kernels/ruby/module.nix @@ -61,6 +61,13 @@ in default = true; }; + lsp.solargraph.formatting = mkOption { + title = "Format Ruby code with rubocop"; + description = "Solargraph formats by running rubocop's autocorrect over the cell, using the configuration below. Turning this off also stops Solargraph from advertising formatting to the editor."; + type = types.bool; + default = true; + }; + lsp.solargraph.rubocopYaml = mkOption { title = "YAML configuration for the rubocop reporter"; type = types.codeMirrorLines "yaml"; diff --git a/modules/kernels/ruby/solargraph.nix b/modules/kernels/ruby/solargraph.nix index 1319a184..9150d656 100644 --- a/modules/kernels/ruby/solargraph.nix +++ b/modules/kernels/ruby/solargraph.nix @@ -5,8 +5,8 @@ , writeTextDir , rubyPackages -, rubocopYaml , kernelName +, settings }: let @@ -23,11 +23,12 @@ let } '' mkdir -p $out/bin makeWrapper ${solargraphRaw}/bin/solargraph $out/bin/solargraph \ - --set XDG_CONFIG_HOME "${writeTextDir "rubocop/config.yml" rubocopYaml}" + --set XDG_CONFIG_HOME "${writeTextDir "rubocop/config.yml" settings.rubocopYaml}" ''; passthru = { inherit languageServerName; + formatters = lib.optional settings.formatting "rubocop"; }; in @@ -45,5 +46,10 @@ common.writeTextDirWithMetaAndPassthru solargraph.meta passthru "lib/codedown/la attrs = ["ruby"]; type = "stream"; args = ["${solargraph}/bin/solargraph" "stdio"]; + # Solargraph only advertises documentFormattingProvider when this is on; it formats with + # rubocop, which is already a dependency of the gem and reads the rubocopYaml above. + initialization_options = { + formatting = settings.formatting; + }; language_id = "ruby"; }]) diff --git a/tests/app/Spec/Tests/Ruby.hs b/tests/app/Spec/Tests/Ruby.hs index ca178872..da236939 100644 --- a/tests/app/Spec/Tests/Ruby.hs +++ b/tests/app/Spec/Tests/Ruby.hs @@ -75,6 +75,13 @@ kernelTests rubyPackage = do info [i|Got ranges: #{Helpers.getDiagnosticRanges diags}|] assertDiagnosticRanges diags [(Range (Position 0 5) (Position 0 12), Just (InR "Style/StringLiterals"))] + -- Only on a .rb file: Solargraph formats by handing the path to rubocop, which won't + -- take a .ipynb. + describe "Formatting" $ + itFormatsAs "solargraph" "test.rb" LanguageKind_Ruby + "def foo( a,b )\n x=a+b\n return x\nend\n" + "def foo(a, b)\n a + b\nend\n" + kernelSpec :: Text -> NixKernelSpec kernelSpec rubyPackage = NixKernelSpec { nixKernelName = kernelName rubyPackage From 6649774d36bec0ff57ff24868ccacaea8f345862 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:30 -0700 Subject: [PATCH 12/34] Rust: put rustfmt on rust-analyzer's PATH so formatting requests stop failing --- modules/kernels/rust/default.nix | 1 + .../language_server_rust_analyzer/config.nix | 4 ++++ tests/app/Spec/Tests/Rust.hs | 2 ++ tests/app/Spec/Tests/Rust/Formatting.hs | 19 +++++++++++++++++++ tests/tests.cabal | 1 + 5 files changed, 27 insertions(+) create mode 100644 tests/app/Spec/Tests/Rust/Formatting.hs diff --git a/modules/kernels/rust/default.nix b/modules/kernels/rust/default.nix index c0617263..6448533e 100644 --- a/modules/kernels/rust/default.nix +++ b/modules/kernels/rust/default.nix @@ -130,5 +130,6 @@ symlinkJoin { code_mirror_mode = "rust"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/kernels/rust/language_server_rust_analyzer/config.nix b/modules/kernels/rust/language_server_rust_analyzer/config.nix index f2815446..67f5727c 100644 --- a/modules/kernels/rust/language_server_rust_analyzer/config.nix +++ b/modules/kernels/rust/language_server_rust_analyzer/config.nix @@ -46,6 +46,7 @@ let passthru = { inherit languageServerName; + formatters = ["rustfmt"]; }; config = { @@ -184,6 +185,9 @@ let "PATH" = lib.makeBinPath [ coreutils rust.packages.stable.cargo + # rust-analyzer shells out to rustfmt for textDocument/formatting, finding it on + # PATH. Without this, formatting is advertised but every request fails. + rust.packages.stable.rustfmt gcc.out ]; diff --git a/tests/app/Spec/Tests/Rust.hs b/tests/app/Spec/Tests/Rust.hs index 41ef89bb..00c63abc 100644 --- a/tests/app/Spec/Tests/Rust.hs +++ b/tests/app/Spec/Tests/Rust.hs @@ -22,6 +22,7 @@ import TestLib.Util import qualified Spec.Tests.Rust.Changes as Changes import qualified Spec.Tests.Rust.Completion as Completion import qualified Spec.Tests.Rust.Diagnostics as Diagnostics +import qualified Spec.Tests.Rust.Formatting as Formatting import qualified Spec.Tests.Rust.Hovers as Hovers import qualified Spec.Tests.Rust.PreIndex as PreIndex import qualified Spec.Tests.Rust.VariableInspector as VariableInspector @@ -61,6 +62,7 @@ tests = describe "Rust" $ do Changes.tests Completion.tests Diagnostics.tests + Formatting.tests Hovers.tests PreIndex.tests diff --git a/tests/app/Spec/Tests/Rust/Formatting.hs b/tests/app/Spec/Tests/Rust/Formatting.hs new file mode 100644 index 00000000..159bdc4f --- /dev/null +++ b/tests/app/Spec/Tests/Rust/Formatting.hs @@ -0,0 +1,19 @@ +module Spec.Tests.Rust.Formatting (tests) where + +import Language.LSP.Protocol.Types +import Test.Sandwich as Sandwich +import TestLib.LSP +import TestLib.Types + + +tests :: (LspContext context m, HasNixEnvironment context) => SpecFree context m () +tests = describe "Formatting" $ + it "rust-analyzer formats with rustfmt" $ do + -- rust-analyzer itself formats fine now that rustfmt is on its PATH, but + -- rust-notebook-language-server forwards textDocument/formatting with the notebook's URI + -- rather than the shadow file's, so rust-analyzer answers "file not found". Everything + -- else is rewritten, which is why the rest of the LSP tests pass. + _ <- pending + formatsAs 300 "rust-analyzer" "test.rs" LanguageKind_Rust + "fn main(){let x = 1+2;println!(\"{}\",x);}\n" + "fn main() {\n let x = 1 + 2;\n println!(\"{}\", x);\n}\n" diff --git a/tests/tests.cabal b/tests/tests.cabal index 4eaf176f..c04cc909 100644 --- a/tests/tests.cabal +++ b/tests/tests.cabal @@ -136,6 +136,7 @@ executable tests Spec.Tests.Rust.Changes Spec.Tests.Rust.Completion Spec.Tests.Rust.Diagnostics + Spec.Tests.Rust.Formatting Spec.Tests.Rust.Hovers Spec.Tests.Rust.PreIndex Spec.Tests.Rust.VariableInspector From 211c18e27fc9ae75461e641be7aaa479aa9a59bd Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:31 -0700 Subject: [PATCH 13/34] Typst: add a setting to pick between typstyle and typstfmt, both vendored into tinymist --- modules/exporters/typst/default.nix | 6 +++++- .../exporters/typst/language_server_tinymist/default.nix | 6 ++++++ modules/exporters/typst/module.nix | 7 +++++++ tests/app/Spec/Tests/Typst.hs | 5 +++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/modules/exporters/typst/default.nix b/modules/exporters/typst/default.nix index 141558a5..f3f81af2 100644 --- a/modules/exporters/typst/default.nix +++ b/modules/exporters/typst/default.nix @@ -41,7 +41,10 @@ let typstToUse = typst.withPackages (ps: (map (x: ps.${x}) packages)); languageServers = lib.optionals settings.lsp.tinymist.enable - [(callPackage ./language_server_tinymist { inherit kernelName typstToUse codedownPackagePath; })]; + [(callPackage ./language_server_tinymist { + inherit kernelName typstToUse codedownPackagePath; + settings = settings.lsp.tinymist; + })]; packageOptions = typst.packages; packageSearch = common.searcher packageOptions; @@ -127,5 +130,6 @@ symlinkJoin { }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/modules/exporters/typst/language_server_tinymist/default.nix b/modules/exporters/typst/language_server_tinymist/default.nix index eaca95c9..99751df6 100644 --- a/modules/exporters/typst/language_server_tinymist/default.nix +++ b/modules/exporters/typst/language_server_tinymist/default.nix @@ -12,6 +12,7 @@ , codedownPackagePath , kernelName +, settings }: let @@ -21,6 +22,7 @@ let passthru = { inherit languageServerName; + formatters = lib.optional (settings.formatter != "none") settings.formatter; }; in @@ -40,5 +42,9 @@ common.writeTextDirWithMetaAndPassthru tinymist.meta passthru "lib/codedown/lang ]; initialization_options = { typstExtraArgs = [ "--package-path=${codedownPackagePath}" ]; + # tinymist has both typstyle and typstfmt vendored and defaults to typstyle, so this only + # picks between them (or turns formatting off). Note that tinymist advertises + # documentFormattingProvider either way and just answers null when it's off. + formatterMode = if settings.formatter == "none" then "disable" else settings.formatter; }; }]) diff --git a/modules/exporters/typst/module.nix b/modules/exporters/typst/module.nix index 23673076..ef6f475b 100644 --- a/modules/exporters/typst/module.nix +++ b/modules/exporters/typst/module.nix @@ -54,6 +54,13 @@ in type = types.bool; default = true; }; + + lsp.tinymist.formatter = mkOption { + title = "Typst formatter"; + description = "Which formatter tinymist uses. Both are vendored into tinymist, so picking one costs nothing extra. typstyle is also tinymist's own default."; + type = types.enum ["typstyle" "typstfmt" "none"]; + default = "typstyle"; + }; }; }; diff --git a/tests/app/Spec/Tests/Typst.hs b/tests/app/Spec/Tests/Typst.hs index 2d41ae3b..6d9bd42d 100644 --- a/tests/app/Spec/Tests/Typst.hs +++ b/tests/app/Spec/Tests/Typst.hs @@ -39,6 +39,11 @@ tests = describe [i|Typst|] $ introduceNixEnvironment [] config [i|Typst|] $ int [__i|\#loremz(45)|] ((== [(Range (Position 0 1) (Position 0 7), Nothing, "unknown variable: loremz")]) . getDiagnosticRanges') + -- typstyle normalizes code mode but leaves markup's whitespace alone. + itFormatsAs lsName "test.typ" (LanguageKind_Custom "typst") + "#let x = 1\n= Heading\nsome text\n" + "#let x = 1\n= Heading\nsome text\n" + documentHighlightCode :: Text documentHighlightCode = [__i|foo = "hello" From 8b01b662e82b62af8b043917c7f375dbb65cfbc9 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 02:48:37 -0700 Subject: [PATCH 14/34] Feature matrix: make the Formatting cell name the formatter behind it --- FEATURE-MATRIX.md | 89 +++++++++++++++++++++++++++---- modules/kernels/r-ark/default.nix | 1 + nix/feature-matrix.nix | 30 +++++++++-- scripts/render-feature-matrix.py | 66 +++++++++++++++++++---- 4 files changed, 161 insertions(+), 25 deletions(-) diff --git a/FEATURE-MATRIX.md b/FEATURE-MATRIX.md index 78ad4a2b..351abdb5 100644 --- a/FEATURE-MATRIX.md +++ b/FEATURE-MATRIX.md @@ -17,6 +17,7 @@ Which languages support which notebook and editor features. The graphic version | Coq | ✅ | ✅ | – | – | ✅ | | Go | ✅ | ✅ | – | – | ✅ | | Haskell | ✅ | ✅ | – | – | ✅ | +| JavaScript | ✅ | ✅ | – | – | ✅ | | Julia | ✅ | ✅ | ✅ | – | ✅ | | Octave | ✅ | ✅ | ✅ | – | ✅ | | PostgreSQL | ✅ | ✅ | – | – | ✅ | @@ -26,6 +27,7 @@ Which languages support which notebook and editor features. The graphic version | R (Ark) | ✅ | ✅ | ✅ | ✅ | ✅ | | Ruby | ✅ | ✅ | ✅ | – | ✅ | | Rust | ✅ | ✅ | ✅ | – | ✅ | +| TypeScript | ✅ | ✅ | – | – | ✅ | ### Code intelligence @@ -35,8 +37,9 @@ Which languages support which notebook and editor features. The graphic version | C++ 23 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Clojure | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | – | | Coq | – | – | – | – | – | – | – | -| Go | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Go | ✅ | ✅ | ✅ | ✅ | ✅ | – | ✅ | | Haskell | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| JavaScript | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Julia | ✅ | ✅ | ✅ | ✅ | ✅ | – | ✅ | | Octave | – | – | – | – | – | – | – | | PostgreSQL | – | – | – | – | – | – | – | @@ -46,6 +49,7 @@ Which languages support which notebook and editor features. The graphic version | R (Ark) | – | – | – | – | – | – | – | | Ruby | ✅ | ✅ | ✅ | ✅ | ✅ | – | – | | Rust | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| TypeScript | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ### Navigation @@ -57,6 +61,7 @@ Which languages support which notebook and editor features. The graphic version | Coq | – | – | – | – | – | – | | Go | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Haskell | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| JavaScript | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Julia | ✅ | – | ✅ | ✅ | ✅ | ✅ | | Octave | – | – | – | – | – | – | | PostgreSQL | – | – | – | – | – | – | @@ -66,26 +71,29 @@ Which languages support which notebook and editor features. The graphic version | R (Ark) | – | – | – | – | – | – | | Ruby | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Rust | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| TypeScript | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ### Editing | Language | Formatting | Rename symbol | Code actions | | --- | --- | --- | --- | -| Bash | ✅ | ✅ | ✅ | -| C++ 23 | ✅ | ✅ | ✅ | -| Clojure | ✅ | ✅ | ✅ | +| Bash | ✅ shfmt | ✅ | ✅ | +| C++ 23 | ✅ clang-format | ✅ | ✅ | +| Clojure | ✅ cljfmt | ✅ | ✅ | | Coq | – | – | – | -| Go | ✅ | ✅ | ✅ | -| Haskell | ✅ | ✅ | ✅ | -| Julia | ✅ | ✅ | ✅ | +| Go | ✅ gofmt | ✅ | ✅ | +| Haskell | ✅ ormolu | ✅ | ✅ | +| JavaScript | ✅ tsserver | ✅ | ✅ | +| Julia | ✅ JuliaFormatter | ✅ | ✅ | | Octave | – | – | – | | PostgreSQL | – | – | – | | PyPy | – | ✅ | ✅ | | Python | – | ✅ | ✅ | -| R | ✅ | ✅ | ✅ | +| R | ✅ styler | ✅ | ✅ | | R (Ark) | – | – | – | -| Ruby | – | ✅ | – | -| Rust | ✅ | ✅ | ✅ | +| Ruby | ✅ rubocop | ✅ | – | +| Rust | ✅ rustfmt | ✅ | ✅ | +| TypeScript | ✅ tsserver | ✅ | ✅ | ### Packages @@ -97,6 +105,7 @@ Which languages support which notebook and editor features. The graphic version | Coq | ✅ | | Go | – | | Haskell | ✅ | +| JavaScript | ✅ | | Julia | ✅ | | Octave | ✅ | | PostgreSQL | – | @@ -106,6 +115,7 @@ Which languages support which notebook and editor features. The graphic version | R (Ark) | ✅ | | Ruby | ✅ | | Rust | ✅ | +| TypeScript | ✅ | @@ -122,6 +132,7 @@ The matrix is derived from the repo, not maintained by hand: builds a single-kernel environment per language, starts each language server, performs the LSP `initialize` handshake, and records the advertised capabilities into `nix/lsp-capabilities.json`. +- The Formatting column needs both halves — see [Formatting](#formatting) below. - `scripts/render-feature-matrix.py` turns the resulting JSON into the SVG in the README and the Markdown tables above. @@ -158,7 +169,63 @@ by anything that wants to render this data elsewhere. Its shape: ``` `level` is `full`, `none`, or `unknown` (nothing probed that kernel yet), and `detail` says -which language server or REPL is behind it. +which language server or REPL is behind it. A feature can also carry `"render": "label"`, +which asks the renderers to print `detail` in the cell rather than a check mark; `formatting` +is the one that does, so its cell names the formatter. + +## Formatting + +Formatting is the one column that can't be read off the `initialize` response alone. A server +will happily advertise `documentFormattingProvider` and then fail every request because the +tool behind it isn't installed — bash-language-server did exactly that until shfmt was added +to its wrapper. So the column is the conjunction of two things: the server advertises +formatting, *and* the kernel names the formatter behind it in `passthru.formatters` (declared +next to each language server's config and aggregated in the kernel's `default.nix`). That name +is what the cell prints. + +| Language | Formatter | Comes from | Setting | +| --- | --- | --- | --- | +| Bash | shfmt | separate binary on bash-language-server's PATH | — | +| C++ | clang-format | built into clangd | — | +| Clojure | cljfmt | built into clojure-lsp | — | +| Go | gofmt, gofumpt | both vendored into gopls | `kernels.go.lsp.gopls.formatter` | +| Haskell | ormolu, fourmolu, stylish-haskell | all compiled into haskell-language-server | `kernels.haskell.lsp.haskell-language-server.formattingProvider` | +| JavaScript / TypeScript | tsserver | built into typescript-language-server | — | +| Julia | JuliaFormatter | dependency of LanguageServer.jl | a `.JuliaFormatter.toml` in the workspace | +| Python | autopep8, yapf, black, ruff | none ship with python-lsp-server | `kernels.python3.lsp.python-lsp-server.formatter` | +| R | styler | already an R dependency of languageserver | — | +| Ruby | rubocop | already a gem dependency of solargraph | `kernels.ruby.lsp.solargraph.formatting` | +| Rust | rustfmt | separate binary on rust-analyzer's PATH | — | +| Typst | typstyle, typstfmt | both vendored into tinymist | `exporters.typst.lsp.tinymist.formatter` | + +Where one formatter is listed and there's no setting, it costs nothing to ship (already in the +server's closure, or a small binary) and is on unconditionally. Where several are listed, the +setting picks between them; they're all built into the server, so the choice doesn't change +what gets built — except for Python, where python-lsp-server ships with no formatter at all and +the setting decides which one is added to the environment. + +Each of these has a test that formats a deliberately misformatted document over LSP and checks +what comes back (`itFormatsAs` in `tests/src/TestLib/LSP.hs`), because advertising the +capability proves nothing. + +Caveats the table above can't show: + +- Python's default language server is Jedi, which has no formatting support of any kind. The + `formatter` setting only applies when `python-lsp-server` is also enabled, which it isn't by + default — so Python's Formatting cell is empty. +- Typst is an exporter rather than a kernel, so tinymist isn't probed and Typst has no row in + the matrix. tinymist already formatted with typstyle by default; the setting exists to pick + typstfmt or turn formatting off. +- Rust formats correctly when rust-analyzer is driven directly, but + rust-notebook-language-server forwards `textDocument/formatting` with the notebook's URI + instead of the shadow file's, so rust-analyzer answers "file not found". Other requests + (`textDocument/documentSymbol`, …) are rewritten and work. That's a fix for the + [rust-notebook-language-server](https://github.com/codedownio/rust-notebook-language-server) + side, not this repo, so the Rust formatting test is marked pending. +- The R formatting test is pending too, for an unrelated reason: the R language server won't + start under the hermetic PATH the test suite uses. Loading processx runs + `system("which uname")`, and R's `system()` goes through `popen`, which wants a `/bin/sh` + the sandbox doesn't provide. styler formats fine outside the sandbox. ## Known gaps diff --git a/modules/kernels/r-ark/default.nix b/modules/kernels/r-ark/default.nix index 63ef7520..34acc7d1 100644 --- a/modules/kernels/r-ark/default.nix +++ b/modules/kernels/r-ark/default.nix @@ -93,5 +93,6 @@ symlinkJoin { code_mirror_mode = "r"; }; languageServerNames = map (x: x.languageServerName) languageServers; + formatters = lib.concatMap (x: x.formatters or []) languageServers; }; } diff --git a/nix/feature-matrix.nix b/nix/feature-matrix.nix index 07e955cd..f274b782 100644 --- a/nix/feature-matrix.nix +++ b/nix/feature-matrix.nix @@ -34,8 +34,12 @@ let { id = "packages"; name = "Packages"; } ]; - # source = "nix": computed from the module evaluation below. - # source = "lsp": read out of the language server's advertised capabilities. + # source = "nix": computed from the module evaluation below. + # source = "lsp": read out of the language server's advertised capabilities. + # source = "both": needs each (see `formatting` below). + # + # render = "label" asks the renderers to print the support detail in the cell rather than + # just a check mark. features = [ { id = "jupyterKernel"; name = "Jupyter kernel"; group = "core"; description = "Runs notebook cells through a Jupyter kernel."; source = "nix"; } @@ -77,7 +81,8 @@ let description = "Highlights other occurrences of the symbol under the cursor."; source = "lsp"; } { id = "formatting"; name = "Formatting"; group = "editing"; - description = "Format a document or a selection."; source = "lsp"; } + description = "Format a document or a selection. The cell names the formatter behind it."; + source = "both"; render = "label"; } { id = "rename"; name = "Rename symbol"; group = "editing"; description = "Rename a symbol and every reference to it."; source = "lsp"; } { id = "codeActions"; name = "Code actions"; group = "editing"; @@ -183,9 +188,24 @@ let subPackageManagement = { level = if hasPackages then "full" else "none"; detail = null; }; }; + # Advertising documentFormattingProvider isn't enough to format: a server can offer it and + # then fail every request because the tool behind it isn't installed, which is exactly what + # bash-language-server did before shfmt joined its wrapper. So formatting counts as + # supported only when a server advertises it *and* the kernel names the formatter behind + # it (`passthru.formatters`, declared next to each language server config). + formatters = kernel.formatters or []; + formattingSupport = + let + advertised = lspSupport name "formatting"; + detail = if formatters == [] then null else lib.concatStringsSep ", " formatters; + in + if formatters == [] + then { level = if advertised.level == "unknown" then "unknown" else "none"; detail = null; } + else { inherit (advertised) level; inherit detail; }; + supportFor = feature: - if feature.source == "lsp" - then lspSupport name feature.id + if feature.id == "formatting" then formattingSupport + else if feature.source == "lsp" then lspSupport name feature.id else nixSupport.${feature.id} or { level = "unknown"; detail = null; }; in { diff --git a/scripts/render-feature-matrix.py b/scripts/render-feature-matrix.py index c740de4e..7f5bc5ea 100755 --- a/scripts/render-feature-matrix.py +++ b/scripts/render-feature-matrix.py @@ -29,6 +29,8 @@ "rowStripe": "#f6f5f2", "yes": "#0ca30c", "yesFill": "#e4f4e4", + # Small text needs more contrast against yesFill than a check mark does. + "yesText": "#0a6e0a", "no": "#c3c2b7", }, "dark": { @@ -41,6 +43,7 @@ "rowStripe": "#201f1e", "yes": "#0ca30c", "yesFill": "#17301a", + "yesText": "#5fd45f", "no": "#4a4a47", }, } @@ -49,6 +52,9 @@ LABEL_WIDTH = 178 COL_WIDTH = 29 +# Rough advance width of the label columns' 9.5px text, used to size those columns. +LABEL_CHAR_WIDTH = 5.3 +LABEL_FONT_SIZE = 9.5 ROW_HEIGHT = 29 GROUP_BAND_HEIGHT = 22 PAD = 16 @@ -72,6 +78,18 @@ def detail_of(language, feature_id): return language["support"].get(feature_id, {}).get("detail") +def is_label_column(feature): + """Columns that print their detail (e.g. the formatter's name) instead of a check.""" + return feature.get("render") == "label" + + +def label_text(language, feature): + """What a label column shows for one language, or None for the not-supported dot.""" + if level_of(language, feature["id"]) != "full": + return None + return detail_of(language, feature["id"]) + + # --------------------------------------------------------------------------- # SVG # --------------------------------------------------------------------------- @@ -101,14 +119,32 @@ def diagonal_extent(name): header_height = int(max(diagonal_extent(f["name"]) for f in ordered)) + 14 + # Check-mark columns are all one width; a label column has to be wide enough for its + # longest value. + def column_width(feature): + if not is_label_column(feature): + return COL_WIDTH + widest = max((len(label_text(l, feature) or "") for l in languages), default=0) + return max(COL_WIDTH, int(widest * LABEL_CHAR_WIDTH) + 14) + + col_widths = [column_width(f) for f in ordered] + col_lefts = [] + running = 0 + for w in col_widths: + col_lefts.append(running) + running += w + grid_left = PAD + LABEL_WIDTH grid_top = PAD + TITLE_HEIGHT + GROUP_BAND_HEIGHT + header_height - grid_width = COL_WIDTH * len(ordered) + grid_width = running grid_height = ROW_HEIGHT * len(languages) + def center_of(i): + return grid_left + col_lefts[i] + col_widths[i] / 2 + # The rotated labels lean up and to the right, so the rightmost one overhangs the grid. # Whichever column's label reaches furthest right sets the canvas width. - rightmost = max(grid_left + i * COL_WIDTH + COL_WIDTH / 2 + 3 + diagonal_extent(f["name"]) + rightmost = max(center_of(i) + 3 + diagonal_extent(f["name"]) for i, f in enumerate(ordered)) width = int(max(rightmost, grid_left + grid_width)) + PAD height = grid_top + grid_height + LEGEND_HEIGHT + PAD @@ -135,8 +171,8 @@ def diagonal_extent(name): column = 0 for group in groups: count = sum(1 for f in ordered if f["group"] == group["id"]) - x = grid_left + column * COL_WIDTH - w = count * COL_WIDTH + x = grid_left + col_lefts[column] + w = sum(col_widths[column:column + count]) add(f' ') add(f' {escape(feature["name"])}') @@ -168,7 +204,7 @@ def diagonal_extent(name): f'text-anchor="end" fill="{theme["muted"]}">{escape(version)}') for i, feature in enumerate(ordered): - cx = grid_left + i * COL_WIDTH + COL_WIDTH / 2 + cx = center_of(i) cy = y + ROW_HEIGHT / 2 level = level_of(language, feature["id"]) detail = detail_of(language, feature["id"]) @@ -179,7 +215,14 @@ def diagonal_extent(name): tooltip += f" ({detail})" add(f' {escape(tooltip)}') - if level == "full": + if is_label_column(feature) and label_text(language, feature): + text = label_text(language, feature) + add(f' ') + add(f' {escape(text)}') + elif level == "full": add(f' ') add(f' ') add(f' Date: Thu, 8 Oct 2026 02:48:37 -0700 Subject: [PATCH 15/34] Regenerate the feature matrix, LSP capabilities and options docs --- OPTIONS.md | 419 +++++++++++++- docs/feature-matrix-dark.svg | 1003 ++++++++++++++++++++-------------- docs/feature-matrix.json | 2 +- docs/feature-matrix.svg | 1003 ++++++++++++++++++++-------------- nix/lsp-capabilities.json | 329 +++++++++-- 5 files changed, 1876 insertions(+), 880 deletions(-) diff --git a/OPTIONS.md b/OPTIONS.md index 85c247b3..5a66ffa7 100644 --- a/OPTIONS.md +++ b/OPTIONS.md @@ -150,6 +150,29 @@ true +## exporters\.typst\.lsp\.tinymist\.formatter + + + +**Typst formatter** + +Which formatter tinymist uses\. Both are vendored into tinymist, so picking one costs nothing extra\. typstyle is also tinymist’s own default\. + + + +*Type:* +one of “typstyle”, “typstfmt”, “none” + + + +*Default:* + +```nix +"typstyle" +``` + + + ## kernels\.R\.interface\.attrs @@ -852,6 +875,29 @@ false +## kernels\.go\.lsp\.gopls\.formatter + + + +**Go formatter** + +Which formatter gopls uses\. Both are built into gopls; gofumpt is gofmt plus a set of stricter rules\. + + + +*Type:* +one of “gofmt”, “gofumpt” + + + +*Default:* + +```nix +"gofmt" +``` + + + ## kernels\.go\.lsp\.gopls\.super-debug @@ -1007,6 +1053,29 @@ false +## kernels\.haskell\.lsp\.haskell-language-server\.formattingProvider + + + +**Haskell formatter** + +Which formatter haskell-language-server uses\. All of these are compiled into it, so picking one costs nothing extra\. + + + +*Type:* +one of “ormolu”, “fourmolu”, “stylish-haskell”, “none” + + + +*Default:* + +```nix +"ormolu" +``` + + + ## kernels\.haskell\.lsp\.haskell-language-server\.super-debug @@ -1028,6 +1097,145 @@ false +## kernels\.javascript\.interface\.attrs + + + +**Notebook attributes** + +Notebook cells that have these attributes will match this kernel, allowing it to run the code\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "javascript" + "js" +] +``` + + + +## kernels\.javascript\.interface\.extensions + + + +**File extensions** + +Files with these extensions will match against this kernel, allowing you to run the code as if it were a Jupyter cell\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "js" + "mjs" + "cjs" +] +``` + + + +## kernels\.javascript\.lsp\.typescript-language-server\.enable + + + +**Enable the TypeScript language server** + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +## kernels\.javascript\.lsp\.typescript-language-server\.checkJs + + + +**Report type errors in JavaScript cells** + +The kernel type checks JavaScript as well as TypeScript, so this keeps the editor’s diagnostics in step with what the kernel will accept\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +## kernels\.javascript\.lsp\.typescript-language-server\.debug + + + +**TypeScript language server: debug output** + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +## kernels\.javascript\.nodejsPackage + + + +**Node\.js version** + + + +*Type:* +one of “nodejs”, “nodejs_22”, “nodejs_24” + + + +*Default:* + +```nix +"nodejs" +``` + + + ## kernels\.julia\.interface\.attrs @@ -1595,6 +1803,29 @@ false +## kernels\.pypy3\.lsp\.python-lsp-server\.formatter + + + +**Python formatter** + +Which formatter python-lsp-server uses\. It ships with none of them, so this also decides what gets added to the environment\. autopep8 is the lightest and reuses the pycodestyle settings above; black and ruff reformat more aggressively\. Note that Jedi, the default language server, can’t format at all – this only applies when python-lsp-server is enabled\. + + + +*Type:* +one of “autopep8”, “yapf”, “black”, “ruff”, “none” + + + +*Default:* + +```nix +"autopep8" +``` + + + ## kernels\.pypy3\.misc\.enableVariableInspector @@ -1881,6 +2112,29 @@ false +## kernels\.python3\.lsp\.python-lsp-server\.formatter + + + +**Python formatter** + +Which formatter python-lsp-server uses\. It ships with none of them, so this also decides what gets added to the environment\. autopep8 is the lightest and reuses the pycodestyle settings above; black and ruff reformat more aggressively\. Note that Jedi, the default language server, can’t format at all – this only applies when python-lsp-server is enabled\. + + + +*Type:* +one of “autopep8”, “yapf”, “black”, “ruff”, “none” + + + +*Default:* + +```nix +"autopep8" +``` + + + ## kernels\.python3\.misc\.enableVariableInspector @@ -2019,9 +2273,30 @@ true -## kernels\.ruby\.lsp\.solargraph\.rubocopYaml +## kernels\.ruby\.lsp\.solargraph\.formatting + + +**Format Ruby code with rubocop** +Solargraph formats by running rubocop’s autocorrect over the cell, using the configuration below\. Turning this off also stops Solargraph from advertising formatting to the editor\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +## kernels\.ruby\.lsp\.solargraph\.rubocopYaml **YAML configuration for the rubocop reporter** @@ -2242,7 +2517,7 @@ true *Type:* -one of “rust”, “rust_1_95” +one of “rust”, “rust_1_95”, “rust_1_97” @@ -2254,6 +2529,144 @@ one of “rust”, “rust_1_95” +## kernels\.typescript\.interface\.attrs + + + +**Notebook attributes** + +Notebook cells that have these attributes will match this kernel, allowing it to run the code\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "typescript" + "ts" +] +``` + + + +## kernels\.typescript\.interface\.extensions + + + +**File extensions** + +Files with these extensions will match against this kernel, allowing you to run the code as if it were a Jupyter cell\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "ts" + "tsx" +] +``` + + + +## kernels\.typescript\.lsp\.typescript-language-server\.enable + + + +**Enable the TypeScript language server** + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +## kernels\.typescript\.lsp\.typescript-language-server\.checkJs + + + +**Report type errors in JavaScript cells** + +The kernel type checks JavaScript as well as TypeScript, so this keeps the editor’s diagnostics in step with what the kernel will accept\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +## kernels\.typescript\.lsp\.typescript-language-server\.debug + + + +**TypeScript language server: debug output** + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +## kernels\.typescript\.nodejsPackage + + + +**Node\.js version** + + + +*Type:* +one of “nodejs”, “nodejs_22”, “nodejs_24” + + + +*Default:* + +```nix +"nodejs" +``` + + + ## language-servers\.spellchecker\.markdown\.enable @@ -2325,6 +2738,8 @@ false ## language-servers\.spellchecker\.typst\.checkLineComments + + **Check Typst line comments** Spellcheck the prose inside Typst line comments (// …)\. diff --git a/docs/feature-matrix-dark.svg b/docs/feature-matrix-dark.svg index 0d31a659..166c2b4b 100644 --- a/docs/feature-matrix-dark.svg +++ b/docs/feature-matrix-dark.svg @@ -1,6 +1,6 @@ - + codedown language feature matrix - + Language feature support Generated from the codedown-languages Nix modules and live language server capabilities @@ -10,10 +10,10 @@ Code intelligence Navigation - - Editing - - Packages + + Editing + + Packages Jupyter kernel REPL Variable inspector @@ -32,11 +32,11 @@ Document outline Workspace symbol search Highlight occurrences - Formatting - Rename symbol - Code actions - Subpackage management - + Formatting + Rename symbol + Code actions + Subpackage management + Bash 5.3p9 Bash - Jupyter kernel: yes @@ -106,20 +106,20 @@ - Bash - Formatting: yes (bash-language-server) - - + Bash - Formatting: yes (shfmt) + + shfmt Bash - Rename symbol: yes (bash-language-server) - - + + Bash - Code actions: yes (bash-language-server) - - + + Bash - Subpackage management: no - + C++ 23 21.1.8 @@ -193,22 +193,22 @@ - C++ 23 - Formatting: yes (clangd) - - + C++ 23 - Formatting: yes (clang-format) + + clang-format C++ 23 - Rename symbol: yes (clangd) - - + + C++ 23 - Code actions: yes (clangd) - - + + C++ 23 - Subpackage management: no - + - + Clojure 1.12.5.1645 Clojure - Jupyter kernel: yes @@ -280,20 +280,20 @@ - Clojure - Formatting: yes (clojure-lsp) - - + Clojure - Formatting: yes (cljfmt) + + cljfmt Clojure - Rename symbol: yes (clojure-lsp) - - + + Clojure - Code actions: yes (clojure-lsp) - - + + Clojure - Subpackage management: no - + Coq 9.1.1 @@ -355,19 +355,19 @@ Coq - Formatting: no - + Coq - Rename symbol: no - + Coq - Code actions: no - + Coq - Subpackage management: yes - - + + - + Go 1.26.6 Go - Jupyter kernel: yes @@ -408,9 +408,8 @@ - Go - Semantic highlighting: yes (gopls) - - + Go - Semantic highlighting: no + Go - Inlay hints: yes (gopls) @@ -440,20 +439,20 @@ - Go - Formatting: yes (gopls) - - + Go - Formatting: yes (gofmt) + + gofmt Go - Rename symbol: yes (gopls) - - + + Go - Code actions: yes (gopls) - - + + Go - Subpackage management: no - + Haskell 9.10.3 @@ -527,775 +526,953 @@ - Haskell - Formatting: yes (haskell-language-server) - - + Haskell - Formatting: yes (ormolu) + + ormolu Haskell - Rename symbol: yes (haskell-language-server) - - + + Haskell - Code actions: yes (haskell-language-server) - - + + Haskell - Subpackage management: yes - - + + - - Julia - 1.12.6 - Julia - Jupyter kernel: yes + + JavaScript + 24.19.0 + JavaScript - Jupyter kernel: yes - Julia - REPL: yes (julia) + JavaScript - REPL: yes (node) - Julia - Variable inspector: yes - - + JavaScript - Variable inspector: no + - Julia - Debugger: no + JavaScript - Debugger: no - Julia - Syntax highlighting: yes (julia) + JavaScript - Syntax highlighting: yes (javascript) - Julia - Language server: yes (LanguageServer) + JavaScript - Language server: yes (typescript-language-server) - Julia - Code completion: yes (LanguageServer) + JavaScript - Code completion: yes (typescript-language-server) - Julia - Hover docs: yes (LanguageServer) + JavaScript - Hover docs: yes (typescript-language-server) - Julia - Signature help: yes (LanguageServer) + JavaScript - Signature help: yes (typescript-language-server) - Julia - Diagnostics: yes (push) + JavaScript - Diagnostics: yes (push) - Julia - Semantic highlighting: no - + JavaScript - Semantic highlighting: yes (typescript-language-server) + + - Julia - Inlay hints: yes (LanguageServer) + JavaScript - Inlay hints: yes (typescript-language-server) - Julia - Jump to definition: yes (LanguageServer) + JavaScript - Jump to definition: yes (typescript-language-server) - Julia - Jump to type definition: no - + JavaScript - Jump to type definition: yes (typescript-language-server) + + - Julia - Find references: yes (LanguageServer) + JavaScript - Find references: yes (typescript-language-server) - Julia - Document outline: yes (LanguageServer) + JavaScript - Document outline: yes (typescript-language-server) - Julia - Workspace symbol search: yes (LanguageServer) + JavaScript - Workspace symbol search: yes (typescript-language-server) - Julia - Highlight occurrences: yes (LanguageServer) + JavaScript - Highlight occurrences: yes (typescript-language-server) - Julia - Formatting: yes (LanguageServer) - - + JavaScript - Formatting: yes (tsserver) + + tsserver - Julia - Rename symbol: yes (LanguageServer) - - + JavaScript - Rename symbol: yes (typescript-language-server) + + - Julia - Code actions: yes (LanguageServer) - - + JavaScript - Code actions: yes (typescript-language-server) + + - Julia - Subpackage management: yes - - + JavaScript - Subpackage management: yes + + - Octave - 11.1.0 - Octave - Jupyter kernel: yes + Julia + 1.12.6 + Julia - Jupyter kernel: yes - Octave - REPL: yes (octave) + Julia - REPL: yes (julia) - Octave - Variable inspector: yes + Julia - Variable inspector: yes - Octave - Debugger: no + Julia - Debugger: no - Octave - Syntax highlighting: yes (octave) + Julia - Syntax highlighting: yes (julia) + Julia - Language server: yes (LanguageServer) + + + + Julia - Code completion: yes (LanguageServer) + + + + Julia - Hover docs: yes (LanguageServer) + + + + Julia - Signature help: yes (LanguageServer) + + + + Julia - Diagnostics: yes (push) + + + + Julia - Semantic highlighting: no + + + Julia - Inlay hints: yes (LanguageServer) + + + + Julia - Jump to definition: yes (LanguageServer) + + + + Julia - Jump to type definition: no + + + Julia - Find references: yes (LanguageServer) + + + + Julia - Document outline: yes (LanguageServer) + + + + Julia - Workspace symbol search: yes (LanguageServer) + + + + Julia - Highlight occurrences: yes (LanguageServer) + + + + Julia - Formatting: yes (JuliaFormatter) + + JuliaFormatter + + Julia - Rename symbol: yes (LanguageServer) + + + + Julia - Code actions: yes (LanguageServer) + + + + Julia - Subpackage management: yes + + + + + Octave + 11.1.0 + Octave - Jupyter kernel: yes + + + + Octave - REPL: yes (octave) + + + + Octave - Variable inspector: yes + + + + Octave - Debugger: no + + + Octave - Syntax highlighting: yes (octave) + + + Octave - Language server: no - + Octave - Code completion: no - + Octave - Hover docs: no - + Octave - Signature help: no - + Octave - Diagnostics: no - + Octave - Semantic highlighting: no - + Octave - Inlay hints: no - + Octave - Jump to definition: no - + Octave - Jump to type definition: no - + Octave - Find references: no - + Octave - Document outline: no - + Octave - Workspace symbol search: no - + Octave - Highlight occurrences: no - + Octave - Formatting: no - + Octave - Rename symbol: no - + Octave - Code actions: no - + Octave - Subpackage management: yes - - + + - - PostgreSQL - 0.1 + PostgreSQL + 0.1 PostgreSQL - Jupyter kernel: yes - - + + PostgreSQL - REPL: yes (console) - - + + PostgreSQL - Variable inspector: no - + PostgreSQL - Debugger: no - + PostgreSQL - Syntax highlighting: yes (sql) - - + + PostgreSQL - Language server: no - + PostgreSQL - Code completion: no - + PostgreSQL - Hover docs: no - + PostgreSQL - Signature help: no - + PostgreSQL - Diagnostics: no - + PostgreSQL - Semantic highlighting: no - + PostgreSQL - Inlay hints: no - + PostgreSQL - Jump to definition: no - + PostgreSQL - Jump to type definition: no - + PostgreSQL - Find references: no - + PostgreSQL - Document outline: no - + PostgreSQL - Workspace symbol search: no - + PostgreSQL - Highlight occurrences: no - + PostgreSQL - Formatting: no - + PostgreSQL - Rename symbol: no - + PostgreSQL - Code actions: no - + PostgreSQL - Subpackage management: no - + - PyPy - 7.3.20 + + PyPy + 7.3.20 PyPy - Jupyter kernel: yes - - + + PyPy - REPL: yes (ipython) - - + + PyPy - Variable inspector: yes - - + + PyPy - Debugger: yes - - + + PyPy - Syntax highlighting: yes (python) - - + + PyPy - Language server: yes (jedi) - - + + PyPy - Code completion: yes (jedi) - - + + PyPy - Hover docs: yes (jedi) - - + + PyPy - Signature help: yes (jedi) - - + + PyPy - Diagnostics: yes (push) - - + + PyPy - Semantic highlighting: no - + PyPy - Inlay hints: no - + PyPy - Jump to definition: yes (jedi) - - + + PyPy - Jump to type definition: yes (jedi) - - + + PyPy - Find references: yes (jedi) - - + + PyPy - Document outline: yes (jedi) - - + + PyPy - Workspace symbol search: yes (jedi) - - + + PyPy - Highlight occurrences: yes (jedi) - - + + PyPy - Formatting: no - + PyPy - Rename symbol: yes (jedi) - - + + PyPy - Code actions: yes (jedi) - - + + PyPy - Subpackage management: yes - - + + - - Python - 3.13.15 + Python + 3.13.15 Python - Jupyter kernel: yes - - + + Python - REPL: yes (ipython) - - + + Python - Variable inspector: yes - - + + Python - Debugger: yes - - + + Python - Syntax highlighting: yes (python) - - + + Python - Language server: yes (jedi) - - + + Python - Code completion: yes (jedi) - - + + Python - Hover docs: yes (jedi) - - + + Python - Signature help: yes (jedi) - - + + Python - Diagnostics: yes (push) - - + + Python - Semantic highlighting: no - + Python - Inlay hints: no - + Python - Jump to definition: yes (jedi) - - + + Python - Jump to type definition: yes (jedi) - - + + Python - Find references: yes (jedi) - - + + Python - Document outline: yes (jedi) - - + + Python - Workspace symbol search: yes (jedi) - - + + Python - Highlight occurrences: yes (jedi) - - + + Python - Formatting: no - + Python - Rename symbol: yes (jedi) - - + + Python - Code actions: yes (jedi) - - + + Python - Subpackage management: yes - - + + - R - 4.5.3 + + R + 4.5.3 R - Jupyter kernel: yes - - + + R - REPL: yes (r) - - + + R - Variable inspector: yes - - + + R - Debugger: no - + R - Syntax highlighting: yes (r) - - + + R - Language server: yes (languageserver) - - + + R - Code completion: yes (languageserver) - - + + R - Hover docs: yes (languageserver) - - + + R - Signature help: yes (languageserver) - - + + R - Diagnostics: yes (push) - - + + R - Semantic highlighting: no - + R - Inlay hints: no - + R - Jump to definition: yes (languageserver) - - + + R - Jump to type definition: no - + R - Find references: yes (languageserver) - - + + R - Document outline: yes (languageserver) - - + + R - Workspace symbol search: yes (languageserver) - - + + R - Highlight occurrences: yes (languageserver) - - + + - R - Formatting: yes (languageserver) - - + R - Formatting: yes (styler) + + styler R - Rename symbol: yes (languageserver) - - + + R - Code actions: yes (languageserver) - - + + R - Subpackage management: yes - - + + - - R (Ark) - 4.5.3 + R (Ark) + 4.5.3 R (Ark) - Jupyter kernel: yes - - + + R (Ark) - REPL: yes (r) - - + + R (Ark) - Variable inspector: yes - - + + R (Ark) - Debugger: yes - - + + R (Ark) - Syntax highlighting: yes (r) - - + + R (Ark) - Language server: no - + R (Ark) - Code completion: no - + R (Ark) - Hover docs: no - + R (Ark) - Signature help: no - + R (Ark) - Diagnostics: no - + R (Ark) - Semantic highlighting: no - + R (Ark) - Inlay hints: no - + R (Ark) - Jump to definition: no - + R (Ark) - Jump to type definition: no - + R (Ark) - Find references: no - + R (Ark) - Document outline: no - + R (Ark) - Workspace symbol search: no - + R (Ark) - Highlight occurrences: no - + R (Ark) - Formatting: no - + R (Ark) - Rename symbol: no - + R (Ark) - Code actions: no - + R (Ark) - Subpackage management: yes - - + + - Ruby - 3.4.9 + + Ruby + 3.4.9 Ruby - Jupyter kernel: yes - - + + Ruby - REPL: yes (irb) - - + + Ruby - Variable inspector: yes - - + + Ruby - Debugger: no - + Ruby - Syntax highlighting: yes (ruby) - - + + Ruby - Language server: yes (solargraph) - - + + Ruby - Code completion: yes (solargraph) - - + + Ruby - Hover docs: yes (solargraph) - - + + Ruby - Signature help: yes (solargraph) - - + + Ruby - Diagnostics: yes (push) - - + + Ruby - Semantic highlighting: no - + Ruby - Inlay hints: no - + Ruby - Jump to definition: yes (solargraph) - - + + Ruby - Jump to type definition: yes (solargraph) - - + + Ruby - Find references: yes (solargraph) - - + + Ruby - Document outline: yes (solargraph) - - + + Ruby - Workspace symbol search: yes (solargraph) - - + + Ruby - Highlight occurrences: yes (solargraph) - - + + - Ruby - Formatting: no - + Ruby - Formatting: yes (rubocop) + + rubocop Ruby - Rename symbol: yes (solargraph) - - + + Ruby - Code actions: no - + Ruby - Subpackage management: yes - - + + - - Rust - 1.95.0 + Rust + 1.95.0 Rust - Jupyter kernel: yes - - + + Rust - REPL: yes (evcxr) - - + + Rust - Variable inspector: yes - - + + Rust - Debugger: no - + Rust - Syntax highlighting: yes (rust) - - + + Rust - Language server: yes (rust-analyzer) - - + + Rust - Code completion: yes (rust-analyzer) - - + + Rust - Hover docs: yes (rust-analyzer) - - + + Rust - Signature help: yes (rust-analyzer) - - + + Rust - Diagnostics: yes (pull + push) - - + + Rust - Semantic highlighting: yes (rust-analyzer) - - + + Rust - Inlay hints: yes (rust-analyzer) - - + + Rust - Jump to definition: yes (rust-analyzer) - - + + Rust - Jump to type definition: yes (rust-analyzer) - - + + Rust - Find references: yes (rust-analyzer) - - + + Rust - Document outline: yes (rust-analyzer) - - + + Rust - Workspace symbol search: yes (rust-analyzer) - - + + Rust - Highlight occurrences: yes (rust-analyzer) - - + + - Rust - Formatting: yes (rust-analyzer) - - + Rust - Formatting: yes (rustfmt) + + rustfmt Rust - Rename symbol: yes (rust-analyzer) - - + + Rust - Code actions: yes (rust-analyzer) - - + + Rust - Subpackage management: yes - - - - - - - - - - - supported - - not available + + + + + TypeScript + 24.19.0 + TypeScript - Jupyter kernel: yes + + + + TypeScript - REPL: yes (node) + + + + TypeScript - Variable inspector: no + + + TypeScript - Debugger: no + + + TypeScript - Syntax highlighting: yes (javascript) + + + + TypeScript - Language server: yes (typescript-language-server) + + + + TypeScript - Code completion: yes (typescript-language-server) + + + + TypeScript - Hover docs: yes (typescript-language-server) + + + + TypeScript - Signature help: yes (typescript-language-server) + + + + TypeScript - Diagnostics: yes (push) + + + + TypeScript - Semantic highlighting: yes (typescript-language-server) + + + + TypeScript - Inlay hints: yes (typescript-language-server) + + + + TypeScript - Jump to definition: yes (typescript-language-server) + + + + TypeScript - Jump to type definition: yes (typescript-language-server) + + + + TypeScript - Find references: yes (typescript-language-server) + + + + TypeScript - Document outline: yes (typescript-language-server) + + + + TypeScript - Workspace symbol search: yes (typescript-language-server) + + + + TypeScript - Highlight occurrences: yes (typescript-language-server) + + + + TypeScript - Formatting: yes (tsserver) + + tsserver + + TypeScript - Rename symbol: yes (typescript-language-server) + + + + TypeScript - Code actions: yes (typescript-language-server) + + + + TypeScript - Subpackage management: yes + + + + + + + + + + + supported + + not available diff --git a/docs/feature-matrix.json b/docs/feature-matrix.json index db0467e9..bb91f26f 100644 --- a/docs/feature-matrix.json +++ b/docs/feature-matrix.json @@ -1 +1 @@ -{"features":[{"description":"Runs notebook cells through a Jupyter kernel.","group":"core","id":"jupyterKernel","name":"Jupyter kernel","source":"nix"},{"description":"Ships an interactive console you can attach to the same session.","group":"core","id":"repl","name":"REPL","source":"nix"},{"description":"Lists the variables currently defined in the session, with drill-down.","group":"core","id":"variableInspector","name":"Variable inspector","source":"nix"},{"description":"Breakpoints and stepping via the Jupyter debug protocol.","group":"core","id":"debugger","name":"Debugger","source":"nix"},{"description":"Editor highlighting for the language.","group":"core","id":"syntaxHighlighting","name":"Syntax highlighting","source":"nix"},{"description":"An LSP server is wired up for notebook cells and files.","group":"intelligence","id":"languageServer","name":"Language server","source":"nix"},{"description":"Context-aware completions as you type.","group":"intelligence","id":"completion","name":"Code completion","source":"lsp"},{"description":"Type and documentation popups on hover.","group":"intelligence","id":"hover","name":"Hover docs","source":"lsp"},{"description":"Parameter hints while calling a function.","group":"intelligence","id":"signatureHelp","name":"Signature help","source":"lsp"},{"description":"Errors, warnings and lints reported inline.","group":"intelligence","id":"diagnostics","name":"Diagnostics","source":"lsp"},{"description":"Highlighting driven by the compiler/analyzer rather than a regex grammar.","group":"intelligence","id":"semanticTokens","name":"Semantic highlighting","source":"lsp"},{"description":"Inferred types and parameter names rendered inline.","group":"intelligence","id":"inlayHints","name":"Inlay hints","source":"lsp"},{"description":"Go from a use of a symbol to where it's defined.","group":"navigation","id":"definition","name":"Jump to definition","source":"lsp"},{"description":"Go from a value to the definition of its type.","group":"navigation","id":"typeDefinition","name":"Jump to type definition","source":"lsp"},{"description":"Find every use of a symbol.","group":"navigation","id":"references","name":"Find references","source":"lsp"},{"description":"Symbol tree for the current document.","group":"navigation","id":"documentSymbols","name":"Document outline","source":"lsp"},{"description":"Search symbols across the whole project.","group":"navigation","id":"workspaceSymbols","name":"Workspace symbol search","source":"lsp"},{"description":"Highlights other occurrences of the symbol under the cursor.","group":"navigation","id":"documentHighlight","name":"Highlight occurrences","source":"lsp"},{"description":"Format a document or a selection.","group":"editing","id":"formatting","name":"Formatting","source":"lsp"},{"description":"Rename a symbol and every reference to it.","group":"editing","id":"rename","name":"Rename symbol","source":"lsp"},{"description":"Quick fixes and refactorings offered on a range.","group":"editing","id":"codeActions","name":"Code actions","source":"lsp"},{"description":"A searchable set of language packages you can add to the environment declaratively.","group":"packages","id":"subPackageManagement","name":"Subpackage management","source":"nix"}],"groups":[{"id":"core","name":"Notebook"},{"id":"intelligence","name":"Code intelligence"},{"id":"navigation","name":"Navigation"},{"id":"editing","name":"Editing"},{"id":"packages","name":"Packages"}],"languages":[{"attrs":["bash"],"componentVersions":{"bash":"5.3p9","bash-kernel":"0.10.0","bash-language-server":"5.6.0"},"description":"GNU Bourne-Again Shell, the de facto standard shell on Linux (for interactive use)","displayName":"Bash","extensions":["sh","bash"],"iconMonochrome":"modules/kernels/bash/gnubash-monochrome.svg","id":"bash","languageServers":{"available":["bash-language-server"],"enabledByDefault":["bash-language-server"],"probed":["bash-language-server"]},"support":{"codeActions":{"detail":"bash-language-server","level":"full"},"completion":{"detail":"bash-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"bash-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"bash-language-server","level":"full"},"documentSymbols":{"detail":"bash-language-server","level":"full"},"formatting":{"detail":"bash-language-server","level":"full"},"hover":{"detail":"bash-language-server","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"bash-language-server","level":"full"},"references":{"detail":"bash-language-server","level":"full"},"rename":{"detail":"bash-language-server","level":"full"},"repl":{"detail":"bash","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"shell","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"bash-language-server","level":"full"}},"version":"5.3p9"},{"attrs":["c++23","cpp"],"componentVersions":{"clang":"21.1.8","clangd":"21.1.8","cling":"1.2","std":"c++23","xeus-cling":"0.15.3"},"description":"C language family frontend for LLVM (wrapper script)","displayName":"C++ 23","extensions":["cpp","hpp","cxx","hxx","c","h"],"iconMonochrome":"modules/kernels/cpp/cpp23-monochrome.svg","id":"cpp","languageServers":{"available":["clangd"],"enabledByDefault":["clangd"],"probed":["clangd"]},"support":{"codeActions":{"detail":"clangd","level":"full"},"completion":{"detail":"clangd","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"clangd","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"clangd","level":"full"},"documentSymbols":{"detail":"clangd","level":"full"},"formatting":{"detail":"clangd","level":"full"},"hover":{"detail":"clangd","level":"full"},"inlayHints":{"detail":"clangd","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"clangd","level":"full"},"references":{"detail":"clangd","level":"full"},"rename":{"detail":"clangd","level":"full"},"repl":{"detail":"cling","level":"full"},"semanticTokens":{"detail":"clangd","level":"full"},"signatureHelp":{"detail":"clangd","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"clike","level":"full"},"typeDefinition":{"detail":"clangd","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"clangd","level":"full"}},"version":"21.1.8"},{"attrs":["clojure"],"componentVersions":{"clojure":"1.12.5.1645","clojure-lsp":"2026.02.20-16.08.58"},"description":"Lisp dialect for the JVM","displayName":"Clojure","extensions":["clj"],"iconMonochrome":"modules/kernels/clojure/clojure-monochrome.svg","id":"clojure","languageServers":{"available":["clojure-lsp"],"enabledByDefault":["clojure-lsp"],"probed":["clojure-lsp"]},"support":{"codeActions":{"detail":"clojure-lsp","level":"full"},"completion":{"detail":"clojure-lsp","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"clojure-lsp","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"clojure-lsp","level":"full"},"documentSymbols":{"detail":"clojure-lsp","level":"full"},"formatting":{"detail":"clojure-lsp","level":"full"},"hover":{"detail":"clojure-lsp","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"clojure-lsp","level":"full"},"references":{"detail":"clojure-lsp","level":"full"},"rename":{"detail":"clojure-lsp","level":"full"},"repl":{"detail":"clojure","level":"full"},"semanticTokens":{"detail":"clojure-lsp","level":"full"},"signatureHelp":{"detail":"clojure-lsp","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"clojure","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"clojure-lsp","level":"full"}},"version":"1.12.5.1645"},{"attrs":["coq"],"componentVersions":{"coq":"9.1.1"},"description":"Coq proof assistant","displayName":"Coq","extensions":["v"],"iconMonochrome":"modules/kernels/coq/coq-monochrome.svg","id":"coq","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"coq","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"9.1.1"},{"attrs":["go"],"componentVersions":{"go":"1.26.6","gopls":"0.22.0"},"description":"Go Programming language","displayName":"Go","extensions":["go"],"iconMonochrome":"modules/kernels/go/go-monochrome.svg","id":"go","languageServers":{"available":["gopls"],"enabledByDefault":["gopls"],"probed":["gopls"]},"support":{"codeActions":{"detail":"gopls","level":"full"},"completion":{"detail":"gopls","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"gopls","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"gopls","level":"full"},"documentSymbols":{"detail":"gopls","level":"full"},"formatting":{"detail":"gopls","level":"full"},"hover":{"detail":"gopls","level":"full"},"inlayHints":{"detail":"gopls","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"gopls","level":"full"},"references":{"detail":"gopls","level":"full"},"rename":{"detail":"gopls","level":"full"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":"gopls","level":"full"},"signatureHelp":{"detail":"gopls","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"go","level":"full"},"typeDefinition":{"detail":"gopls","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"gopls","level":"full"}},"version":"1.26.6"},{"attrs":["haskell"],"componentVersions":{"ghc":"9.10.3","haskell-language-server":"2.13.0.0"},"description":"An advanced, purely functional programming language (GHC 9.10.3)","displayName":"Haskell","extensions":["hs"],"iconMonochrome":"modules/kernels/haskell/haskell-monochrome.svg","id":"haskell","languageServers":{"available":["haskell-language-server"],"enabledByDefault":["haskell-language-server"],"probed":["haskell-language-server"]},"support":{"codeActions":{"detail":"haskell-language-server","level":"full"},"completion":{"detail":"haskell-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"haskell-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"haskell-language-server","level":"full"},"documentSymbols":{"detail":"haskell-language-server","level":"full"},"formatting":{"detail":"haskell-language-server","level":"full"},"hover":{"detail":"haskell-language-server","level":"full"},"inlayHints":{"detail":"haskell-language-server","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"haskell-language-server","level":"full"},"references":{"detail":"haskell-language-server","level":"full"},"rename":{"detail":"haskell-language-server","level":"full"},"repl":{"detail":"ghci","level":"full"},"semanticTokens":{"detail":"haskell-language-server","level":"full"},"signatureHelp":{"detail":"haskell-language-server","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"haskell","level":"full"},"typeDefinition":{"detail":"haskell-language-server","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"haskell-language-server","level":"full"}},"version":"9.10.3"},{"attrs":["julia"],"componentVersions":{"julia":"1.12.6"},"description":"High-level performance-oriented dynamical language for technical computing","displayName":"Julia","extensions":["jl"],"iconMonochrome":"modules/kernels/julia/julia-monochrome.svg","id":"julia","languageServers":{"available":["LanguageServer"],"enabledByDefault":["LanguageServer"],"probed":["LanguageServer"]},"support":{"codeActions":{"detail":"LanguageServer","level":"full"},"completion":{"detail":"LanguageServer","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"LanguageServer","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"LanguageServer","level":"full"},"documentSymbols":{"detail":"LanguageServer","level":"full"},"formatting":{"detail":"LanguageServer","level":"full"},"hover":{"detail":"LanguageServer","level":"full"},"inlayHints":{"detail":"LanguageServer","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"LanguageServer","level":"full"},"references":{"detail":"LanguageServer","level":"full"},"rename":{"detail":"LanguageServer","level":"full"},"repl":{"detail":"julia","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"LanguageServer","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"julia","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"LanguageServer","level":"full"}},"version":"1.12.6"},{"attrs":["octave"],"componentVersions":{"octave":"11.1.0"},"description":"Scientific Programming Language","displayName":"Octave","extensions":["m"],"iconMonochrome":"modules/kernels/octave/octave-monochrome.svg","id":"octave","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"octave","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"octave","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"11.1.0"},{"attrs":["postgres"],"componentVersions":{"postgres-kernel":"0.1"},"description":"A simple Jupyter kernel for PostgreSQL","displayName":"PostgreSQL","extensions":["sql"],"iconMonochrome":"modules/kernels/postgres/postgresql-monochrome.svg","id":"postgres","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"sql","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"0.1"},{"attrs":["python3","python"],"componentVersions":{"flake8":"7.3.0","jedi-language-server":"0.46.0","pycodestyle":"2.14.0","pylint":"4.0.5","pyright":"1.1.411","python":"7.3.20","python-lsp-server":"1.14.0"},"description":"Fast, compliant alternative implementation of the Python language (3.11)","displayName":"PyPy","extensions":["py"],"iconMonochrome":"modules/kernels/python/python-monochrome.svg","id":"pypy3","languageServers":{"available":["flake8","jedi","microsoft","pycodestyle","pylint","pyright","python-language-server","python-lsp-server"],"enabledByDefault":["jedi"],"probed":["jedi"]},"support":{"codeActions":{"detail":"jedi","level":"full"},"completion":{"detail":"jedi","level":"full"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":"jedi","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"jedi","level":"full"},"documentSymbols":{"detail":"jedi","level":"full"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":"jedi","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"jedi","level":"full"},"references":{"detail":"jedi","level":"full"},"rename":{"detail":"jedi","level":"full"},"repl":{"detail":"ipython","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"jedi","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"python","level":"full"},"typeDefinition":{"detail":"jedi","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"jedi","level":"full"}},"version":"7.3.20"},{"attrs":["python3","python"],"componentVersions":{"flake8":"7.3.0","jedi-language-server":"0.46.0","pycodestyle":"2.14.0","pylint":"4.0.5","pyright":"1.1.411","python":"3.13.15","python-lsp-server":"1.14.0"},"description":"High-level dynamically-typed programming language","displayName":"Python","extensions":["py"],"iconMonochrome":"modules/kernels/python/python-monochrome.svg","id":"python3","languageServers":{"available":["flake8","jedi","microsoft","pycodestyle","pylint","pyright","python-language-server","python-lsp-server"],"enabledByDefault":["jedi"],"probed":["jedi"]},"support":{"codeActions":{"detail":"jedi","level":"full"},"completion":{"detail":"jedi","level":"full"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":"jedi","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"jedi","level":"full"},"documentSymbols":{"detail":"jedi","level":"full"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":"jedi","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"jedi","level":"full"},"references":{"detail":"jedi","level":"full"},"rename":{"detail":"jedi","level":"full"},"repl":{"detail":"ipython","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"jedi","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"python","level":"full"},"typeDefinition":{"detail":"jedi","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"jedi","level":"full"}},"version":"3.13.15"},{"attrs":["r","R"],"componentVersions":{"languageserver":"0.3.16","r":"4.5.3"},"description":"Free software environment for statistical computing and graphics","displayName":"R","extensions":["r"],"iconMonochrome":"modules/kernels/r/r-monochrome.svg","id":"R","languageServers":{"available":["languageserver"],"enabledByDefault":["languageserver"],"probed":["languageserver"]},"support":{"codeActions":{"detail":"languageserver","level":"full"},"completion":{"detail":"languageserver","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"languageserver","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"languageserver","level":"full"},"documentSymbols":{"detail":"languageserver","level":"full"},"formatting":{"detail":"languageserver","level":"full"},"hover":{"detail":"languageserver","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"languageserver","level":"full"},"references":{"detail":"languageserver","level":"full"},"rename":{"detail":"languageserver","level":"full"},"repl":{"detail":"r","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"languageserver","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"r","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"languageserver","level":"full"}},"version":"4.5.3"},{"attrs":["r","R"],"componentVersions":{"ark":"0.1.252","r":"4.5.3"},"description":"Free software environment for statistical computing and graphics","displayName":"R (Ark)","extensions":["r"],"iconMonochrome":"modules/kernels/r-ark/r-monochrome.svg","id":"R-ark","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"r","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"r","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"4.5.3"},{"attrs":["ruby"],"componentVersions":{"iruby":"v0.8.2","ruby":"3.4.9","solargraph":"0.57.0"},"description":"Object-oriented language for quick and easy programming","displayName":"Ruby","extensions":["rb"],"iconMonochrome":"modules/kernels/ruby/ruby-monochrome.svg","id":"ruby","languageServers":{"available":["solargraph"],"enabledByDefault":["solargraph"],"probed":["solargraph"]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":"solargraph","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"solargraph","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"solargraph","level":"full"},"documentSymbols":{"detail":"solargraph","level":"full"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":"solargraph","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"solargraph","level":"full"},"references":{"detail":"solargraph","level":"full"},"rename":{"detail":"solargraph","level":"full"},"repl":{"detail":"irb","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"solargraph","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"ruby","level":"full"},"typeDefinition":{"detail":"solargraph","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"solargraph","level":"full"}},"version":"3.4.9"},{"attrs":["rust"],"componentVersions":{"cargo":"1.95.0","evcxr":"0.21.1","rust":"1.95.0","rust-analyzer":"2026-06-01"},"description":"Safe, concurrent, practical language (wrapper script)","displayName":"Rust","extensions":["rs","rlib"],"iconMonochrome":"modules/kernels/rust/rust-logo-monochrome.svg","id":"rust","languageServers":{"available":["rust-analyzer"],"enabledByDefault":["rust-analyzer"],"probed":["rust-analyzer"]},"support":{"codeActions":{"detail":"rust-analyzer","level":"full"},"completion":{"detail":"rust-analyzer","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"rust-analyzer","level":"full"},"diagnostics":{"detail":"pull + push","level":"full"},"documentHighlight":{"detail":"rust-analyzer","level":"full"},"documentSymbols":{"detail":"rust-analyzer","level":"full"},"formatting":{"detail":"rust-analyzer","level":"full"},"hover":{"detail":"rust-analyzer","level":"full"},"inlayHints":{"detail":"rust-analyzer","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"rust-analyzer","level":"full"},"references":{"detail":"rust-analyzer","level":"full"},"rename":{"detail":"rust-analyzer","level":"full"},"repl":{"detail":"evcxr","level":"full"},"semanticTokens":{"detail":"rust-analyzer","level":"full"},"signatureHelp":{"detail":"rust-analyzer","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"rust","level":"full"},"typeDefinition":{"detail":"rust-analyzer","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"rust-analyzer","level":"full"}},"version":"1.95.0"}],"provenance":{"lsp":"Read from language server `initialize` responses (nix/lsp-capabilities.json, regenerated by scripts/probe-lsp-capabilities).","lspProbedKernels":["R","R-ark","bash","clojure","coq","cpp","go","haskell","julia","octave","postgres","pypy3","python3","ruby","rust"],"nix":"Evaluated from the codedown-languages module system (nix/feature-matrix.nix)."},"schemaVersion":1} \ No newline at end of file +{"features":[{"description":"Runs notebook cells through a Jupyter kernel.","group":"core","id":"jupyterKernel","name":"Jupyter kernel","source":"nix"},{"description":"Ships an interactive console you can attach to the same session.","group":"core","id":"repl","name":"REPL","source":"nix"},{"description":"Lists the variables currently defined in the session, with drill-down.","group":"core","id":"variableInspector","name":"Variable inspector","source":"nix"},{"description":"Breakpoints and stepping via the Jupyter debug protocol.","group":"core","id":"debugger","name":"Debugger","source":"nix"},{"description":"Editor highlighting for the language.","group":"core","id":"syntaxHighlighting","name":"Syntax highlighting","source":"nix"},{"description":"An LSP server is wired up for notebook cells and files.","group":"intelligence","id":"languageServer","name":"Language server","source":"nix"},{"description":"Context-aware completions as you type.","group":"intelligence","id":"completion","name":"Code completion","source":"lsp"},{"description":"Type and documentation popups on hover.","group":"intelligence","id":"hover","name":"Hover docs","source":"lsp"},{"description":"Parameter hints while calling a function.","group":"intelligence","id":"signatureHelp","name":"Signature help","source":"lsp"},{"description":"Errors, warnings and lints reported inline.","group":"intelligence","id":"diagnostics","name":"Diagnostics","source":"lsp"},{"description":"Highlighting driven by the compiler/analyzer rather than a regex grammar.","group":"intelligence","id":"semanticTokens","name":"Semantic highlighting","source":"lsp"},{"description":"Inferred types and parameter names rendered inline.","group":"intelligence","id":"inlayHints","name":"Inlay hints","source":"lsp"},{"description":"Go from a use of a symbol to where it's defined.","group":"navigation","id":"definition","name":"Jump to definition","source":"lsp"},{"description":"Go from a value to the definition of its type.","group":"navigation","id":"typeDefinition","name":"Jump to type definition","source":"lsp"},{"description":"Find every use of a symbol.","group":"navigation","id":"references","name":"Find references","source":"lsp"},{"description":"Symbol tree for the current document.","group":"navigation","id":"documentSymbols","name":"Document outline","source":"lsp"},{"description":"Search symbols across the whole project.","group":"navigation","id":"workspaceSymbols","name":"Workspace symbol search","source":"lsp"},{"description":"Highlights other occurrences of the symbol under the cursor.","group":"navigation","id":"documentHighlight","name":"Highlight occurrences","source":"lsp"},{"description":"Format a document or a selection. The cell names the formatter behind it.","group":"editing","id":"formatting","name":"Formatting","render":"label","source":"both"},{"description":"Rename a symbol and every reference to it.","group":"editing","id":"rename","name":"Rename symbol","source":"lsp"},{"description":"Quick fixes and refactorings offered on a range.","group":"editing","id":"codeActions","name":"Code actions","source":"lsp"},{"description":"A searchable set of language packages you can add to the environment declaratively.","group":"packages","id":"subPackageManagement","name":"Subpackage management","source":"nix"}],"groups":[{"id":"core","name":"Notebook"},{"id":"intelligence","name":"Code intelligence"},{"id":"navigation","name":"Navigation"},{"id":"editing","name":"Editing"},{"id":"packages","name":"Packages"}],"languages":[{"attrs":["bash"],"componentVersions":{"bash":"5.3p9","bash-kernel":"0.10.0","bash-language-server":"5.6.0"},"description":"GNU Bourne-Again Shell, the de facto standard shell on Linux (for interactive use)","displayName":"Bash","extensions":["sh","bash"],"iconMonochrome":"modules/kernels/bash/gnubash-monochrome.svg","id":"bash","languageServers":{"available":["bash-language-server"],"enabledByDefault":["bash-language-server"],"probed":["bash-language-server"]},"support":{"codeActions":{"detail":"bash-language-server","level":"full"},"completion":{"detail":"bash-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"bash-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"bash-language-server","level":"full"},"documentSymbols":{"detail":"bash-language-server","level":"full"},"formatting":{"detail":"shfmt","level":"full"},"hover":{"detail":"bash-language-server","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"bash-language-server","level":"full"},"references":{"detail":"bash-language-server","level":"full"},"rename":{"detail":"bash-language-server","level":"full"},"repl":{"detail":"bash","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"shell","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"bash-language-server","level":"full"}},"version":"5.3p9"},{"attrs":["c++23","cpp"],"componentVersions":{"clang":"21.1.8","clangd":"21.1.8","cling":"1.2","std":"c++23","xeus-cling":"0.15.3"},"description":"C language family frontend for LLVM (wrapper script)","displayName":"C++ 23","extensions":["cpp","hpp","cxx","hxx","c","h"],"iconMonochrome":"modules/kernels/cpp/cpp23-monochrome.svg","id":"cpp","languageServers":{"available":["clangd"],"enabledByDefault":["clangd"],"probed":["clangd"]},"support":{"codeActions":{"detail":"clangd","level":"full"},"completion":{"detail":"clangd","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"clangd","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"clangd","level":"full"},"documentSymbols":{"detail":"clangd","level":"full"},"formatting":{"detail":"clang-format","level":"full"},"hover":{"detail":"clangd","level":"full"},"inlayHints":{"detail":"clangd","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"clangd","level":"full"},"references":{"detail":"clangd","level":"full"},"rename":{"detail":"clangd","level":"full"},"repl":{"detail":"cling","level":"full"},"semanticTokens":{"detail":"clangd","level":"full"},"signatureHelp":{"detail":"clangd","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"clike","level":"full"},"typeDefinition":{"detail":"clangd","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"clangd","level":"full"}},"version":"21.1.8"},{"attrs":["clojure"],"componentVersions":{"clojure":"1.12.5.1645","clojure-lsp":"2026.02.20-16.08.58"},"description":"Lisp dialect for the JVM","displayName":"Clojure","extensions":["clj"],"iconMonochrome":"modules/kernels/clojure/clojure-monochrome.svg","id":"clojure","languageServers":{"available":["clojure-lsp"],"enabledByDefault":["clojure-lsp"],"probed":["clojure-lsp"]},"support":{"codeActions":{"detail":"clojure-lsp","level":"full"},"completion":{"detail":"clojure-lsp","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"clojure-lsp","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"clojure-lsp","level":"full"},"documentSymbols":{"detail":"clojure-lsp","level":"full"},"formatting":{"detail":"cljfmt","level":"full"},"hover":{"detail":"clojure-lsp","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"clojure-lsp","level":"full"},"references":{"detail":"clojure-lsp","level":"full"},"rename":{"detail":"clojure-lsp","level":"full"},"repl":{"detail":"clojure","level":"full"},"semanticTokens":{"detail":"clojure-lsp","level":"full"},"signatureHelp":{"detail":"clojure-lsp","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"clojure","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"clojure-lsp","level":"full"}},"version":"1.12.5.1645"},{"attrs":["coq"],"componentVersions":{"coq":"9.1.1"},"description":"Coq proof assistant","displayName":"Coq","extensions":["v"],"iconMonochrome":"modules/kernels/coq/coq-monochrome.svg","id":"coq","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"coq","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"9.1.1"},{"attrs":["go"],"componentVersions":{"go":"1.26.6","gopls":"0.22.0"},"description":"Go Programming language","displayName":"Go","extensions":["go"],"iconMonochrome":"modules/kernels/go/go-monochrome.svg","id":"go","languageServers":{"available":["gopls"],"enabledByDefault":["gopls"],"probed":["gopls"]},"support":{"codeActions":{"detail":"gopls","level":"full"},"completion":{"detail":"gopls","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"gopls","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"gopls","level":"full"},"documentSymbols":{"detail":"gopls","level":"full"},"formatting":{"detail":"gofmt","level":"full"},"hover":{"detail":"gopls","level":"full"},"inlayHints":{"detail":"gopls","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"gopls","level":"full"},"references":{"detail":"gopls","level":"full"},"rename":{"detail":"gopls","level":"full"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"gopls","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"go","level":"full"},"typeDefinition":{"detail":"gopls","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"gopls","level":"full"}},"version":"1.26.6"},{"attrs":["haskell"],"componentVersions":{"ghc":"9.10.3","haskell-language-server":"2.13.0.0"},"description":"An advanced, purely functional programming language (GHC 9.10.3)","displayName":"Haskell","extensions":["hs"],"iconMonochrome":"modules/kernels/haskell/haskell-monochrome.svg","id":"haskell","languageServers":{"available":["haskell-language-server"],"enabledByDefault":["haskell-language-server"],"probed":["haskell-language-server"]},"support":{"codeActions":{"detail":"haskell-language-server","level":"full"},"completion":{"detail":"haskell-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"haskell-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"haskell-language-server","level":"full"},"documentSymbols":{"detail":"haskell-language-server","level":"full"},"formatting":{"detail":"ormolu","level":"full"},"hover":{"detail":"haskell-language-server","level":"full"},"inlayHints":{"detail":"haskell-language-server","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"haskell-language-server","level":"full"},"references":{"detail":"haskell-language-server","level":"full"},"rename":{"detail":"haskell-language-server","level":"full"},"repl":{"detail":"ghci","level":"full"},"semanticTokens":{"detail":"haskell-language-server","level":"full"},"signatureHelp":{"detail":"haskell-language-server","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"haskell","level":"full"},"typeDefinition":{"detail":"haskell-language-server","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"haskell-language-server","level":"full"}},"version":"9.10.3"},{"attrs":["javascript","js"],"componentVersions":{"nodejs":"24.19.0","tslab":"1.0.22"},"description":"Event-driven I/O framework for the V8 JavaScript engine","displayName":"JavaScript","extensions":["js","mjs","cjs"],"iconMonochrome":"modules/kernels/javascript/javascript-monochrome.svg","id":"javascript","languageServers":{"available":["typescript-language-server"],"enabledByDefault":["typescript-language-server"],"probed":["typescript-language-server"]},"support":{"codeActions":{"detail":"typescript-language-server","level":"full"},"completion":{"detail":"typescript-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"typescript-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"typescript-language-server","level":"full"},"documentSymbols":{"detail":"typescript-language-server","level":"full"},"formatting":{"detail":"tsserver","level":"full"},"hover":{"detail":"typescript-language-server","level":"full"},"inlayHints":{"detail":"typescript-language-server","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"typescript-language-server","level":"full"},"references":{"detail":"typescript-language-server","level":"full"},"rename":{"detail":"typescript-language-server","level":"full"},"repl":{"detail":"node","level":"full"},"semanticTokens":{"detail":"typescript-language-server","level":"full"},"signatureHelp":{"detail":"typescript-language-server","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"javascript","level":"full"},"typeDefinition":{"detail":"typescript-language-server","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"typescript-language-server","level":"full"}},"version":"24.19.0"},{"attrs":["julia"],"componentVersions":{"julia":"1.12.6"},"description":"High-level performance-oriented dynamical language for technical computing","displayName":"Julia","extensions":["jl"],"iconMonochrome":"modules/kernels/julia/julia-monochrome.svg","id":"julia","languageServers":{"available":["LanguageServer"],"enabledByDefault":["LanguageServer"],"probed":["LanguageServer"]},"support":{"codeActions":{"detail":"LanguageServer","level":"full"},"completion":{"detail":"LanguageServer","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"LanguageServer","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"LanguageServer","level":"full"},"documentSymbols":{"detail":"LanguageServer","level":"full"},"formatting":{"detail":"JuliaFormatter","level":"full"},"hover":{"detail":"LanguageServer","level":"full"},"inlayHints":{"detail":"LanguageServer","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"LanguageServer","level":"full"},"references":{"detail":"LanguageServer","level":"full"},"rename":{"detail":"LanguageServer","level":"full"},"repl":{"detail":"julia","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"LanguageServer","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"julia","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"LanguageServer","level":"full"}},"version":"1.12.6"},{"attrs":["octave"],"componentVersions":{"octave":"11.1.0"},"description":"Scientific Programming Language","displayName":"Octave","extensions":["m"],"iconMonochrome":"modules/kernels/octave/octave-monochrome.svg","id":"octave","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"octave","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"octave","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"11.1.0"},{"attrs":["postgres"],"componentVersions":{"postgres-kernel":"0.1"},"description":"A simple Jupyter kernel for PostgreSQL","displayName":"PostgreSQL","extensions":["sql"],"iconMonochrome":"modules/kernels/postgres/postgresql-monochrome.svg","id":"postgres","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"sql","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"0.1"},{"attrs":["python3","python"],"componentVersions":{"flake8":"7.3.0","jedi-language-server":"0.46.0","pycodestyle":"2.14.0","pylint":"4.0.5","pyright":"1.1.411","python":"7.3.20","python-lsp-server":"1.14.0"},"description":"Fast, compliant alternative implementation of the Python language (3.11)","displayName":"PyPy","extensions":["py"],"iconMonochrome":"modules/kernels/python/python-monochrome.svg","id":"pypy3","languageServers":{"available":["flake8","jedi","microsoft","pycodestyle","pylint","pyright","python-language-server","python-lsp-server"],"enabledByDefault":["jedi"],"probed":["jedi"]},"support":{"codeActions":{"detail":"jedi","level":"full"},"completion":{"detail":"jedi","level":"full"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":"jedi","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"jedi","level":"full"},"documentSymbols":{"detail":"jedi","level":"full"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":"jedi","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"jedi","level":"full"},"references":{"detail":"jedi","level":"full"},"rename":{"detail":"jedi","level":"full"},"repl":{"detail":"ipython","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"jedi","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"python","level":"full"},"typeDefinition":{"detail":"jedi","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"jedi","level":"full"}},"version":"7.3.20"},{"attrs":["python3","python"],"componentVersions":{"flake8":"7.3.0","jedi-language-server":"0.46.0","pycodestyle":"2.14.0","pylint":"4.0.5","pyright":"1.1.411","python":"3.13.15","python-lsp-server":"1.14.0"},"description":"High-level dynamically-typed programming language","displayName":"Python","extensions":["py"],"iconMonochrome":"modules/kernels/python/python-monochrome.svg","id":"python3","languageServers":{"available":["flake8","jedi","microsoft","pycodestyle","pylint","pyright","python-language-server","python-lsp-server"],"enabledByDefault":["jedi"],"probed":["jedi"]},"support":{"codeActions":{"detail":"jedi","level":"full"},"completion":{"detail":"jedi","level":"full"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":"jedi","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"jedi","level":"full"},"documentSymbols":{"detail":"jedi","level":"full"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":"jedi","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"jedi","level":"full"},"references":{"detail":"jedi","level":"full"},"rename":{"detail":"jedi","level":"full"},"repl":{"detail":"ipython","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"jedi","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"python","level":"full"},"typeDefinition":{"detail":"jedi","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"jedi","level":"full"}},"version":"3.13.15"},{"attrs":["r","R"],"componentVersions":{"languageserver":"0.3.16","r":"4.5.3"},"description":"Free software environment for statistical computing and graphics","displayName":"R","extensions":["r"],"iconMonochrome":"modules/kernels/r/r-monochrome.svg","id":"R","languageServers":{"available":["languageserver"],"enabledByDefault":["languageserver"],"probed":["languageserver"]},"support":{"codeActions":{"detail":"languageserver","level":"full"},"completion":{"detail":"languageserver","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"languageserver","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"languageserver","level":"full"},"documentSymbols":{"detail":"languageserver","level":"full"},"formatting":{"detail":"styler","level":"full"},"hover":{"detail":"languageserver","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"languageserver","level":"full"},"references":{"detail":"languageserver","level":"full"},"rename":{"detail":"languageserver","level":"full"},"repl":{"detail":"r","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"languageserver","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"r","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"languageserver","level":"full"}},"version":"4.5.3"},{"attrs":["r","R"],"componentVersions":{"ark":"0.1.252","r":"4.5.3"},"description":"Free software environment for statistical computing and graphics","displayName":"R (Ark)","extensions":["r"],"iconMonochrome":"modules/kernels/r-ark/r-monochrome.svg","id":"R-ark","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"r","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"r","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"4.5.3"},{"attrs":["ruby"],"componentVersions":{"iruby":"v0.8.2","ruby":"3.4.9","solargraph":"0.57.0"},"description":"Object-oriented language for quick and easy programming","displayName":"Ruby","extensions":["rb"],"iconMonochrome":"modules/kernels/ruby/ruby-monochrome.svg","id":"ruby","languageServers":{"available":["solargraph"],"enabledByDefault":["solargraph"],"probed":["solargraph"]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":"solargraph","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"solargraph","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"solargraph","level":"full"},"documentSymbols":{"detail":"solargraph","level":"full"},"formatting":{"detail":"rubocop","level":"full"},"hover":{"detail":"solargraph","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"solargraph","level":"full"},"references":{"detail":"solargraph","level":"full"},"rename":{"detail":"solargraph","level":"full"},"repl":{"detail":"irb","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"solargraph","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"ruby","level":"full"},"typeDefinition":{"detail":"solargraph","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"solargraph","level":"full"}},"version":"3.4.9"},{"attrs":["rust"],"componentVersions":{"cargo":"1.95.0","evcxr":"0.21.1","rust":"1.95.0","rust-analyzer":"2026-06-01"},"description":"Safe, concurrent, practical language (wrapper script)","displayName":"Rust","extensions":["rs","rlib"],"iconMonochrome":"modules/kernels/rust/rust-logo-monochrome.svg","id":"rust","languageServers":{"available":["rust-analyzer"],"enabledByDefault":["rust-analyzer"],"probed":["rust-analyzer"]},"support":{"codeActions":{"detail":"rust-analyzer","level":"full"},"completion":{"detail":"rust-analyzer","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"rust-analyzer","level":"full"},"diagnostics":{"detail":"pull + push","level":"full"},"documentHighlight":{"detail":"rust-analyzer","level":"full"},"documentSymbols":{"detail":"rust-analyzer","level":"full"},"formatting":{"detail":"rustfmt","level":"full"},"hover":{"detail":"rust-analyzer","level":"full"},"inlayHints":{"detail":"rust-analyzer","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"rust-analyzer","level":"full"},"references":{"detail":"rust-analyzer","level":"full"},"rename":{"detail":"rust-analyzer","level":"full"},"repl":{"detail":"evcxr","level":"full"},"semanticTokens":{"detail":"rust-analyzer","level":"full"},"signatureHelp":{"detail":"rust-analyzer","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"rust","level":"full"},"typeDefinition":{"detail":"rust-analyzer","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"rust-analyzer","level":"full"}},"version":"1.95.0"},{"attrs":["typescript","ts"],"componentVersions":{"nodejs":"24.19.0","tslab":"1.0.22"},"description":"Event-driven I/O framework for the V8 JavaScript engine","displayName":"TypeScript","extensions":["ts","tsx"],"iconMonochrome":"modules/kernels/typescript/typescript-monochrome.svg","id":"typescript","languageServers":{"available":["typescript-language-server"],"enabledByDefault":["typescript-language-server"],"probed":["typescript-language-server"]},"support":{"codeActions":{"detail":"typescript-language-server","level":"full"},"completion":{"detail":"typescript-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"typescript-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"typescript-language-server","level":"full"},"documentSymbols":{"detail":"typescript-language-server","level":"full"},"formatting":{"detail":"tsserver","level":"full"},"hover":{"detail":"typescript-language-server","level":"full"},"inlayHints":{"detail":"typescript-language-server","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"typescript-language-server","level":"full"},"references":{"detail":"typescript-language-server","level":"full"},"rename":{"detail":"typescript-language-server","level":"full"},"repl":{"detail":"node","level":"full"},"semanticTokens":{"detail":"typescript-language-server","level":"full"},"signatureHelp":{"detail":"typescript-language-server","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"javascript","level":"full"},"typeDefinition":{"detail":"typescript-language-server","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"typescript-language-server","level":"full"}},"version":"24.19.0"}],"provenance":{"lsp":"Read from language server `initialize` responses (nix/lsp-capabilities.json, regenerated by scripts/probe-lsp-capabilities).","lspProbedKernels":["R","R-ark","bash","clojure","coq","cpp","go","haskell","javascript","julia","octave","postgres","pypy3","python3","ruby","rust","typescript"],"nix":"Evaluated from the codedown-languages module system (nix/feature-matrix.nix)."},"schemaVersion":1} \ No newline at end of file diff --git a/docs/feature-matrix.svg b/docs/feature-matrix.svg index 9aba1638..b7b5be43 100644 --- a/docs/feature-matrix.svg +++ b/docs/feature-matrix.svg @@ -1,6 +1,6 @@ - + codedown language feature matrix - + Language feature support Generated from the codedown-languages Nix modules and live language server capabilities @@ -10,10 +10,10 @@ Code intelligence Navigation - - Editing - - Packages + + Editing + + Packages Jupyter kernel REPL Variable inspector @@ -32,11 +32,11 @@ Document outline Workspace symbol search Highlight occurrences - Formatting - Rename symbol - Code actions - Subpackage management - + Formatting + Rename symbol + Code actions + Subpackage management + Bash 5.3p9 Bash - Jupyter kernel: yes @@ -106,20 +106,20 @@ - Bash - Formatting: yes (bash-language-server) - - + Bash - Formatting: yes (shfmt) + + shfmt Bash - Rename symbol: yes (bash-language-server) - - + + Bash - Code actions: yes (bash-language-server) - - + + Bash - Subpackage management: no - + C++ 23 21.1.8 @@ -193,22 +193,22 @@ - C++ 23 - Formatting: yes (clangd) - - + C++ 23 - Formatting: yes (clang-format) + + clang-format C++ 23 - Rename symbol: yes (clangd) - - + + C++ 23 - Code actions: yes (clangd) - - + + C++ 23 - Subpackage management: no - + - + Clojure 1.12.5.1645 Clojure - Jupyter kernel: yes @@ -280,20 +280,20 @@ - Clojure - Formatting: yes (clojure-lsp) - - + Clojure - Formatting: yes (cljfmt) + + cljfmt Clojure - Rename symbol: yes (clojure-lsp) - - + + Clojure - Code actions: yes (clojure-lsp) - - + + Clojure - Subpackage management: no - + Coq 9.1.1 @@ -355,19 +355,19 @@ Coq - Formatting: no - + Coq - Rename symbol: no - + Coq - Code actions: no - + Coq - Subpackage management: yes - - + + - + Go 1.26.6 Go - Jupyter kernel: yes @@ -408,9 +408,8 @@ - Go - Semantic highlighting: yes (gopls) - - + Go - Semantic highlighting: no + Go - Inlay hints: yes (gopls) @@ -440,20 +439,20 @@ - Go - Formatting: yes (gopls) - - + Go - Formatting: yes (gofmt) + + gofmt Go - Rename symbol: yes (gopls) - - + + Go - Code actions: yes (gopls) - - + + Go - Subpackage management: no - + Haskell 9.10.3 @@ -527,775 +526,953 @@ - Haskell - Formatting: yes (haskell-language-server) - - + Haskell - Formatting: yes (ormolu) + + ormolu Haskell - Rename symbol: yes (haskell-language-server) - - + + Haskell - Code actions: yes (haskell-language-server) - - + + Haskell - Subpackage management: yes - - + + - - Julia - 1.12.6 - Julia - Jupyter kernel: yes + + JavaScript + 24.19.0 + JavaScript - Jupyter kernel: yes - Julia - REPL: yes (julia) + JavaScript - REPL: yes (node) - Julia - Variable inspector: yes - - + JavaScript - Variable inspector: no + - Julia - Debugger: no + JavaScript - Debugger: no - Julia - Syntax highlighting: yes (julia) + JavaScript - Syntax highlighting: yes (javascript) - Julia - Language server: yes (LanguageServer) + JavaScript - Language server: yes (typescript-language-server) - Julia - Code completion: yes (LanguageServer) + JavaScript - Code completion: yes (typescript-language-server) - Julia - Hover docs: yes (LanguageServer) + JavaScript - Hover docs: yes (typescript-language-server) - Julia - Signature help: yes (LanguageServer) + JavaScript - Signature help: yes (typescript-language-server) - Julia - Diagnostics: yes (push) + JavaScript - Diagnostics: yes (push) - Julia - Semantic highlighting: no - + JavaScript - Semantic highlighting: yes (typescript-language-server) + + - Julia - Inlay hints: yes (LanguageServer) + JavaScript - Inlay hints: yes (typescript-language-server) - Julia - Jump to definition: yes (LanguageServer) + JavaScript - Jump to definition: yes (typescript-language-server) - Julia - Jump to type definition: no - + JavaScript - Jump to type definition: yes (typescript-language-server) + + - Julia - Find references: yes (LanguageServer) + JavaScript - Find references: yes (typescript-language-server) - Julia - Document outline: yes (LanguageServer) + JavaScript - Document outline: yes (typescript-language-server) - Julia - Workspace symbol search: yes (LanguageServer) + JavaScript - Workspace symbol search: yes (typescript-language-server) - Julia - Highlight occurrences: yes (LanguageServer) + JavaScript - Highlight occurrences: yes (typescript-language-server) - Julia - Formatting: yes (LanguageServer) - - + JavaScript - Formatting: yes (tsserver) + + tsserver - Julia - Rename symbol: yes (LanguageServer) - - + JavaScript - Rename symbol: yes (typescript-language-server) + + - Julia - Code actions: yes (LanguageServer) - - + JavaScript - Code actions: yes (typescript-language-server) + + - Julia - Subpackage management: yes - - + JavaScript - Subpackage management: yes + + - Octave - 11.1.0 - Octave - Jupyter kernel: yes + Julia + 1.12.6 + Julia - Jupyter kernel: yes - Octave - REPL: yes (octave) + Julia - REPL: yes (julia) - Octave - Variable inspector: yes + Julia - Variable inspector: yes - Octave - Debugger: no + Julia - Debugger: no - Octave - Syntax highlighting: yes (octave) + Julia - Syntax highlighting: yes (julia) + Julia - Language server: yes (LanguageServer) + + + + Julia - Code completion: yes (LanguageServer) + + + + Julia - Hover docs: yes (LanguageServer) + + + + Julia - Signature help: yes (LanguageServer) + + + + Julia - Diagnostics: yes (push) + + + + Julia - Semantic highlighting: no + + + Julia - Inlay hints: yes (LanguageServer) + + + + Julia - Jump to definition: yes (LanguageServer) + + + + Julia - Jump to type definition: no + + + Julia - Find references: yes (LanguageServer) + + + + Julia - Document outline: yes (LanguageServer) + + + + Julia - Workspace symbol search: yes (LanguageServer) + + + + Julia - Highlight occurrences: yes (LanguageServer) + + + + Julia - Formatting: yes (JuliaFormatter) + + JuliaFormatter + + Julia - Rename symbol: yes (LanguageServer) + + + + Julia - Code actions: yes (LanguageServer) + + + + Julia - Subpackage management: yes + + + + + Octave + 11.1.0 + Octave - Jupyter kernel: yes + + + + Octave - REPL: yes (octave) + + + + Octave - Variable inspector: yes + + + + Octave - Debugger: no + + + Octave - Syntax highlighting: yes (octave) + + + Octave - Language server: no - + Octave - Code completion: no - + Octave - Hover docs: no - + Octave - Signature help: no - + Octave - Diagnostics: no - + Octave - Semantic highlighting: no - + Octave - Inlay hints: no - + Octave - Jump to definition: no - + Octave - Jump to type definition: no - + Octave - Find references: no - + Octave - Document outline: no - + Octave - Workspace symbol search: no - + Octave - Highlight occurrences: no - + Octave - Formatting: no - + Octave - Rename symbol: no - + Octave - Code actions: no - + Octave - Subpackage management: yes - - + + - - PostgreSQL - 0.1 + PostgreSQL + 0.1 PostgreSQL - Jupyter kernel: yes - - + + PostgreSQL - REPL: yes (console) - - + + PostgreSQL - Variable inspector: no - + PostgreSQL - Debugger: no - + PostgreSQL - Syntax highlighting: yes (sql) - - + + PostgreSQL - Language server: no - + PostgreSQL - Code completion: no - + PostgreSQL - Hover docs: no - + PostgreSQL - Signature help: no - + PostgreSQL - Diagnostics: no - + PostgreSQL - Semantic highlighting: no - + PostgreSQL - Inlay hints: no - + PostgreSQL - Jump to definition: no - + PostgreSQL - Jump to type definition: no - + PostgreSQL - Find references: no - + PostgreSQL - Document outline: no - + PostgreSQL - Workspace symbol search: no - + PostgreSQL - Highlight occurrences: no - + PostgreSQL - Formatting: no - + PostgreSQL - Rename symbol: no - + PostgreSQL - Code actions: no - + PostgreSQL - Subpackage management: no - + - PyPy - 7.3.20 + + PyPy + 7.3.20 PyPy - Jupyter kernel: yes - - + + PyPy - REPL: yes (ipython) - - + + PyPy - Variable inspector: yes - - + + PyPy - Debugger: yes - - + + PyPy - Syntax highlighting: yes (python) - - + + PyPy - Language server: yes (jedi) - - + + PyPy - Code completion: yes (jedi) - - + + PyPy - Hover docs: yes (jedi) - - + + PyPy - Signature help: yes (jedi) - - + + PyPy - Diagnostics: yes (push) - - + + PyPy - Semantic highlighting: no - + PyPy - Inlay hints: no - + PyPy - Jump to definition: yes (jedi) - - + + PyPy - Jump to type definition: yes (jedi) - - + + PyPy - Find references: yes (jedi) - - + + PyPy - Document outline: yes (jedi) - - + + PyPy - Workspace symbol search: yes (jedi) - - + + PyPy - Highlight occurrences: yes (jedi) - - + + PyPy - Formatting: no - + PyPy - Rename symbol: yes (jedi) - - + + PyPy - Code actions: yes (jedi) - - + + PyPy - Subpackage management: yes - - + + - - Python - 3.13.15 + Python + 3.13.15 Python - Jupyter kernel: yes - - + + Python - REPL: yes (ipython) - - + + Python - Variable inspector: yes - - + + Python - Debugger: yes - - + + Python - Syntax highlighting: yes (python) - - + + Python - Language server: yes (jedi) - - + + Python - Code completion: yes (jedi) - - + + Python - Hover docs: yes (jedi) - - + + Python - Signature help: yes (jedi) - - + + Python - Diagnostics: yes (push) - - + + Python - Semantic highlighting: no - + Python - Inlay hints: no - + Python - Jump to definition: yes (jedi) - - + + Python - Jump to type definition: yes (jedi) - - + + Python - Find references: yes (jedi) - - + + Python - Document outline: yes (jedi) - - + + Python - Workspace symbol search: yes (jedi) - - + + Python - Highlight occurrences: yes (jedi) - - + + Python - Formatting: no - + Python - Rename symbol: yes (jedi) - - + + Python - Code actions: yes (jedi) - - + + Python - Subpackage management: yes - - + + - R - 4.5.3 + + R + 4.5.3 R - Jupyter kernel: yes - - + + R - REPL: yes (r) - - + + R - Variable inspector: yes - - + + R - Debugger: no - + R - Syntax highlighting: yes (r) - - + + R - Language server: yes (languageserver) - - + + R - Code completion: yes (languageserver) - - + + R - Hover docs: yes (languageserver) - - + + R - Signature help: yes (languageserver) - - + + R - Diagnostics: yes (push) - - + + R - Semantic highlighting: no - + R - Inlay hints: no - + R - Jump to definition: yes (languageserver) - - + + R - Jump to type definition: no - + R - Find references: yes (languageserver) - - + + R - Document outline: yes (languageserver) - - + + R - Workspace symbol search: yes (languageserver) - - + + R - Highlight occurrences: yes (languageserver) - - + + - R - Formatting: yes (languageserver) - - + R - Formatting: yes (styler) + + styler R - Rename symbol: yes (languageserver) - - + + R - Code actions: yes (languageserver) - - + + R - Subpackage management: yes - - + + - - R (Ark) - 4.5.3 + R (Ark) + 4.5.3 R (Ark) - Jupyter kernel: yes - - + + R (Ark) - REPL: yes (r) - - + + R (Ark) - Variable inspector: yes - - + + R (Ark) - Debugger: yes - - + + R (Ark) - Syntax highlighting: yes (r) - - + + R (Ark) - Language server: no - + R (Ark) - Code completion: no - + R (Ark) - Hover docs: no - + R (Ark) - Signature help: no - + R (Ark) - Diagnostics: no - + R (Ark) - Semantic highlighting: no - + R (Ark) - Inlay hints: no - + R (Ark) - Jump to definition: no - + R (Ark) - Jump to type definition: no - + R (Ark) - Find references: no - + R (Ark) - Document outline: no - + R (Ark) - Workspace symbol search: no - + R (Ark) - Highlight occurrences: no - + R (Ark) - Formatting: no - + R (Ark) - Rename symbol: no - + R (Ark) - Code actions: no - + R (Ark) - Subpackage management: yes - - + + - Ruby - 3.4.9 + + Ruby + 3.4.9 Ruby - Jupyter kernel: yes - - + + Ruby - REPL: yes (irb) - - + + Ruby - Variable inspector: yes - - + + Ruby - Debugger: no - + Ruby - Syntax highlighting: yes (ruby) - - + + Ruby - Language server: yes (solargraph) - - + + Ruby - Code completion: yes (solargraph) - - + + Ruby - Hover docs: yes (solargraph) - - + + Ruby - Signature help: yes (solargraph) - - + + Ruby - Diagnostics: yes (push) - - + + Ruby - Semantic highlighting: no - + Ruby - Inlay hints: no - + Ruby - Jump to definition: yes (solargraph) - - + + Ruby - Jump to type definition: yes (solargraph) - - + + Ruby - Find references: yes (solargraph) - - + + Ruby - Document outline: yes (solargraph) - - + + Ruby - Workspace symbol search: yes (solargraph) - - + + Ruby - Highlight occurrences: yes (solargraph) - - + + - Ruby - Formatting: no - + Ruby - Formatting: yes (rubocop) + + rubocop Ruby - Rename symbol: yes (solargraph) - - + + Ruby - Code actions: no - + Ruby - Subpackage management: yes - - + + - - Rust - 1.95.0 + Rust + 1.95.0 Rust - Jupyter kernel: yes - - + + Rust - REPL: yes (evcxr) - - + + Rust - Variable inspector: yes - - + + Rust - Debugger: no - + Rust - Syntax highlighting: yes (rust) - - + + Rust - Language server: yes (rust-analyzer) - - + + Rust - Code completion: yes (rust-analyzer) - - + + Rust - Hover docs: yes (rust-analyzer) - - + + Rust - Signature help: yes (rust-analyzer) - - + + Rust - Diagnostics: yes (pull + push) - - + + Rust - Semantic highlighting: yes (rust-analyzer) - - + + Rust - Inlay hints: yes (rust-analyzer) - - + + Rust - Jump to definition: yes (rust-analyzer) - - + + Rust - Jump to type definition: yes (rust-analyzer) - - + + Rust - Find references: yes (rust-analyzer) - - + + Rust - Document outline: yes (rust-analyzer) - - + + Rust - Workspace symbol search: yes (rust-analyzer) - - + + Rust - Highlight occurrences: yes (rust-analyzer) - - + + - Rust - Formatting: yes (rust-analyzer) - - + Rust - Formatting: yes (rustfmt) + + rustfmt Rust - Rename symbol: yes (rust-analyzer) - - + + Rust - Code actions: yes (rust-analyzer) - - + + Rust - Subpackage management: yes - - - - - - - - - - - supported - - not available + + + + + TypeScript + 24.19.0 + TypeScript - Jupyter kernel: yes + + + + TypeScript - REPL: yes (node) + + + + TypeScript - Variable inspector: no + + + TypeScript - Debugger: no + + + TypeScript - Syntax highlighting: yes (javascript) + + + + TypeScript - Language server: yes (typescript-language-server) + + + + TypeScript - Code completion: yes (typescript-language-server) + + + + TypeScript - Hover docs: yes (typescript-language-server) + + + + TypeScript - Signature help: yes (typescript-language-server) + + + + TypeScript - Diagnostics: yes (push) + + + + TypeScript - Semantic highlighting: yes (typescript-language-server) + + + + TypeScript - Inlay hints: yes (typescript-language-server) + + + + TypeScript - Jump to definition: yes (typescript-language-server) + + + + TypeScript - Jump to type definition: yes (typescript-language-server) + + + + TypeScript - Find references: yes (typescript-language-server) + + + + TypeScript - Document outline: yes (typescript-language-server) + + + + TypeScript - Workspace symbol search: yes (typescript-language-server) + + + + TypeScript - Highlight occurrences: yes (typescript-language-server) + + + + TypeScript - Formatting: yes (tsserver) + + tsserver + + TypeScript - Rename symbol: yes (typescript-language-server) + + + + TypeScript - Code actions: yes (typescript-language-server) + + + + TypeScript - Subpackage management: yes + + + + + + + + + + + supported + + not available diff --git a/nix/lsp-capabilities.json b/nix/lsp-capabilities.json index 74190fca..6127fe30 100644 --- a/nix/lsp-capabilities.json +++ b/nix/lsp-capabilities.json @@ -415,9 +415,11 @@ "refactor.extract.variable-all", "refactor.inline.call", "refactor.inline.variable", + "refactor.move.moveType", "refactor.rewrite.changeQuote", "refactor.rewrite.fillStruct", "refactor.rewrite.fillSwitch", + "refactor.rewrite.implementInterface", "refactor.rewrite.invertIf", "refactor.rewrite.joinLines", "refactor.rewrite.removeUnusedParam", @@ -462,12 +464,15 @@ "gopls.gc_details", "gopls.generate", "gopls.go_get_package", + "gopls.implement_interface", "gopls.list_imports", "gopls.list_known_packages", + "gopls.lsp", "gopls.maybe_prompt_for_telemetry", "gopls.mem_stats", "gopls.modify_tags", "gopls.modules", + "gopls.move_type", "gopls.package_symbols", "gopls.packages", "gopls.regenerate_cgo", @@ -490,6 +495,11 @@ "gopls.workspace_stats" ] }, + "experimental": { + "interactiveResolveProvider": [ + "command" + ] + }, "foldingRangeProvider": true, "hoverProvider": true, "implementationProvider": true, @@ -499,46 +509,10 @@ "prepareProvider": true }, "selectionRangeProvider": true, - "semanticTokensProvider": { - "full": true, - "legend": { - "tokenModifiers": [ - "definition", - "readonly", - "defaultLibrary", - "array", - "bool", - "chan", - "format", - "interface", - "map", - "number", - "pointer", - "signature", - "slice", - "string", - "struct" - ], - "tokenTypes": [ - "namespace", - "type", - "typeParameter", - "parameter", - "variable", - "function", - "method", - "macro", - "keyword", - "comment", - "string", - "number", - "operator", - "label" - ] - }, - "range": true - }, "signatureHelpProvider": { + "retriggerCharacters": [ + ")" + ], "triggerCharacters": [ "(", "," @@ -572,7 +546,7 @@ "workspaceSymbolProvider": true }, "displayName": "gopls", - "version": "0.20.0" + "version": "0.22.0" } }, "haskell": { @@ -613,17 +587,17 @@ }, "executeCommandProvider": { "commands": [ - "1622900:cabalHaskellIntegration:cabalAddDependency", - "1622900:cabalHaskellIntegration:cabalAddModule", - "1622900:ghcide-extend-import-action:extendImport", - "1622900:explicit-fields:codeActionResolve", - "1622900:eval:evalCommand", - "1622900:ghcide-type-lenses:typesignature.add", - "1622900:gadt:GADT.toGADT", - "1622900:importLens:ImportLensCommand", - "1622900:class:classplugin.codeaction", - "1622900:class:classplugin.typelens", - "1622900:moduleName:updateModuleName" + "429494:cabalHaskellIntegration:cabalAddDependency", + "429494:cabalHaskellIntegration:cabalAddModule", + "429494:ghcide-extend-import-action:extendImport", + "429494:explicit-fields:codeActionResolve", + "429494:eval:evalCommand", + "429494:ghcide-type-lenses:typesignature.add", + "429494:gadt:GADT.toGADT", + "429494:importLens:ImportLensCommand", + "429494:class:classplugin.codeaction", + "429494:class:classplugin.typelens", + "429494:moduleName:updateModuleName" ], "workDoneProgress": false }, @@ -722,6 +696,132 @@ "version": "2.13.0.0" } }, + "javascript": { + "typescript-language-server": { + "capabilities": { + "callHierarchyProvider": true, + "codeActionProvider": { + "codeActionKinds": [ + "source.fixAll.ts", + "source.removeUnused.ts", + "source.addMissingImports.ts", + "quickfix", + "source.organizeImports.ts", + "source.removeUnusedImports.ts", + "source.sortImports.ts", + "refactor" + ] + }, + "codeLensProvider": { + "resolveProvider": true + }, + "completionProvider": { + "resolveProvider": true, + "triggerCharacters": [ + ".", + "\"", + "'", + "/", + "@", + "<" + ] + }, + "definitionProvider": true, + "documentFormattingProvider": true, + "documentHighlightProvider": true, + "documentRangeFormattingProvider": true, + "documentSymbolProvider": true, + "executeCommandProvider": { + "commands": [ + "_typescript.applyRefactoring", + "_typescript.configurePlugin", + "_typescript.organizeImports", + "_typescript.applyRenameFile", + "_typescript.goToSourceDefinition", + "_typescript.applyCodeActionCommand", + "_typescript.applyFixAllCodeAction", + "typescript.tsserverRequest" + ] + }, + "foldingRangeProvider": true, + "hoverProvider": true, + "implementationProvider": true, + "inlayHintProvider": true, + "linkedEditingRangeProvider": true, + "referencesProvider": true, + "renameProvider": { + "prepareProvider": true + }, + "selectionRangeProvider": true, + "semanticTokensProvider": { + "documentSelector": null, + "full": true, + "legend": { + "tokenModifiers": [ + "declaration", + "static", + "async", + "readonly", + "defaultLibrary", + "local" + ], + "tokenTypes": [ + "class", + "enum", + "interface", + "namespace", + "typeParameter", + "type", + "parameter", + "variable", + "enumMember", + "property", + "function", + "member" + ] + }, + "range": true + }, + "signatureHelpProvider": { + "retriggerCharacters": [ + ")" + ], + "triggerCharacters": [ + "(", + ",", + "<" + ] + }, + "textDocumentSync": 2, + "typeDefinitionProvider": true, + "workspace": { + "fileOperations": { + "willRename": { + "filters": [ + { + "pattern": { + "glob": "**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}", + "matches": "file" + }, + "scheme": "file" + }, + { + "pattern": { + "glob": "**", + "matches": "folder" + }, + "scheme": "file" + } + ] + } + } + }, + "workspaceSymbolProvider": true + }, + "displayName": "TypeScript language server", + "version": "5.3.0" + } + }, "julia": { "LanguageServer": { "capabilities": { @@ -929,6 +1029,7 @@ ] }, "definitionProvider": true, + "documentFormattingProvider": true, "documentHighlightProvider": true, "documentSymbolProvider": true, "foldingRangeProvider": true, @@ -1177,6 +1278,132 @@ "displayName": "rust-analyzer", "version": "2026-04-27" } + }, + "typescript": { + "typescript-language-server": { + "capabilities": { + "callHierarchyProvider": true, + "codeActionProvider": { + "codeActionKinds": [ + "source.fixAll.ts", + "source.removeUnused.ts", + "source.addMissingImports.ts", + "quickfix", + "source.organizeImports.ts", + "source.removeUnusedImports.ts", + "source.sortImports.ts", + "refactor" + ] + }, + "codeLensProvider": { + "resolveProvider": true + }, + "completionProvider": { + "resolveProvider": true, + "triggerCharacters": [ + ".", + "\"", + "'", + "/", + "@", + "<" + ] + }, + "definitionProvider": true, + "documentFormattingProvider": true, + "documentHighlightProvider": true, + "documentRangeFormattingProvider": true, + "documentSymbolProvider": true, + "executeCommandProvider": { + "commands": [ + "_typescript.applyRefactoring", + "_typescript.configurePlugin", + "_typescript.organizeImports", + "_typescript.applyRenameFile", + "_typescript.goToSourceDefinition", + "_typescript.applyCodeActionCommand", + "_typescript.applyFixAllCodeAction", + "typescript.tsserverRequest" + ] + }, + "foldingRangeProvider": true, + "hoverProvider": true, + "implementationProvider": true, + "inlayHintProvider": true, + "linkedEditingRangeProvider": true, + "referencesProvider": true, + "renameProvider": { + "prepareProvider": true + }, + "selectionRangeProvider": true, + "semanticTokensProvider": { + "documentSelector": null, + "full": true, + "legend": { + "tokenModifiers": [ + "declaration", + "static", + "async", + "readonly", + "defaultLibrary", + "local" + ], + "tokenTypes": [ + "class", + "enum", + "interface", + "namespace", + "typeParameter", + "type", + "parameter", + "variable", + "enumMember", + "property", + "function", + "member" + ] + }, + "range": true + }, + "signatureHelpProvider": { + "retriggerCharacters": [ + ")" + ], + "triggerCharacters": [ + "(", + ",", + "<" + ] + }, + "textDocumentSync": 2, + "typeDefinitionProvider": true, + "workspace": { + "fileOperations": { + "willRename": { + "filters": [ + { + "pattern": { + "glob": "**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}", + "matches": "file" + }, + "scheme": "file" + }, + { + "pattern": { + "glob": "**", + "matches": "folder" + }, + "scheme": "file" + } + ] + } + } + }, + "workspaceSymbolProvider": true + }, + "displayName": "TypeScript language server", + "version": "5.3.0" + } } } } From dca86faa001d54c6c543ad8b54f6282b3fc77509 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 20:15:02 -0700 Subject: [PATCH 16/34] Tests: give the sandbox a /bin/sh and grep, and keep newlines out of test directory names --- tests/flake.nix | 7 ++++++- tests/src/TestLib/LSP.hs | 5 ++++- tests/stack.yaml | 4 ++-- tests/stack.yaml.lock | 6 +++--- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/flake.nix b/tests/flake.nix index e443d808..e4c74ffc 100644 --- a/tests/flake.nix +++ b/tests/flake.nix @@ -75,8 +75,13 @@ # Print a trivial PATH that we can use to run kernel and LSP tests, to ensure # they aren't depending on anything on the test machine's PATH. + # + # grep is here because R's parallel::detectCores() shells out to + # `grep "^processor" /proc/cpuinfo | wc -l`. Without it the count comes back + # empty, detectCores() returns NA, and the R language server dies working out + # how big to make its session pool. print-basic-path = pkgs.writeShellScriptBin "basic-path.sh" '' - echo ${pkgs.lib.makeBinPath (with pkgs; [coreutils bash])} + echo ${pkgs.lib.makeBinPath (with pkgs; [coreutils bash gnugrep])} ''; inherit (pkgs) direnv stack; diff --git a/tests/src/TestLib/LSP.hs b/tests/src/TestLib/LSP.hs index f0465e6b..b10f5f1b 100644 --- a/tests/src/TestLib/LSP.hs +++ b/tests/src/TestLib/LSP.hs @@ -188,8 +188,11 @@ itReformats name filename languageKind code = itFormats' :: ( LspContext ctx m, HasNixEnvironment ctx ) => Double -> Text -> FilePath -> LanguageKind -> Text -> (Text -> ExampleT ctx m ()) -> SpecFree ctx m () +-- Deliberately keeps the code out of the test name: Sandwich names the test's directory after +-- it, and the escaped newlines turn into backslashes in a path, which R.cache rewrites to +-- forward slashes and then can't write to. itFormats' timeoutSeconds name filename languageKind code cb = - it [i|#{name}: #{show code} (formatting)|] $ + it [i|#{name} formats #{filename}|] $ formats' timeoutSeconds name filename languageKind code cb -- | The body of 'itFormatsAs', for specs that need to wrap it (e.g. to mark it 'pending'). diff --git a/tests/stack.yaml b/tests/stack.yaml index a3f5de3d..388e9103 100644 --- a/tests/stack.yaml +++ b/tests/stack.yaml @@ -27,7 +27,7 @@ extra-deps: - lsp-types - git: https://github.com/codedownio/lsp-test-helpers.git - # main branch - commit: 3aead859c272db11879cec58bef0f4e65244be45 + # kernel-piggyback-and-binsh branch, for the /bin/sh the R language server needs at startup + commit: aef37ba837a82582e84ea06f6e6bb1d168b21b68 - ex-pool-0.2.1@sha256:c8249338ced27bc4d6395ad9c3069eec394fb111813d6ec736814d095f7e6a24,1293 diff --git a/tests/stack.yaml.lock b/tests/stack.yaml.lock index 656fa660..d4203646 100644 --- a/tests/stack.yaml.lock +++ b/tests/stack.yaml.lock @@ -70,15 +70,15 @@ packages: git: https://github.com/codedownio/lsp.git subdir: lsp-types - completed: - commit: 3aead859c272db11879cec58bef0f4e65244be45 + commit: aef37ba837a82582e84ea06f6e6bb1d168b21b68 git: https://github.com/codedownio/lsp-test-helpers.git name: lsp-test-helpers pantry-tree: - sha256: e54dd03d9c54245b2996523858d8812206eef6e3a3f90fb3d1820a789590e421 + sha256: cc4c78a54a0e259107af495fdd8e61d2362a26e1bb22f67bb5c4b4257fd2434e size: 1025 version: 0.1.0.0 original: - commit: 3aead859c272db11879cec58bef0f4e65244be45 + commit: aef37ba837a82582e84ea06f6e6bb1d168b21b68 git: https://github.com/codedownio/lsp-test-helpers.git - completed: hackage: ex-pool-0.2.1@sha256:c8249338ced27bc4d6395ad9c3069eec394fb111813d6ec736814d095f7e6a24,1293 From 62f42566033699cb512ad9e802e380dafb84b99c Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 20:15:02 -0700 Subject: [PATCH 17/34] R: the language server starts now; note what still keeps its formatting test pending --- FEATURE-MATRIX.md | 7 +++---- tests/app/Spec/Tests/R.hs | 12 +++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/FEATURE-MATRIX.md b/FEATURE-MATRIX.md index 351abdb5..9816391e 100644 --- a/FEATURE-MATRIX.md +++ b/FEATURE-MATRIX.md @@ -222,10 +222,9 @@ Caveats the table above can't show: (`textDocument/documentSymbol`, …) are rewritten and work. That's a fix for the [rust-notebook-language-server](https://github.com/codedownio/rust-notebook-language-server) side, not this repo, so the Rust formatting test is marked pending. -- The R formatting test is pending too, for an unrelated reason: the R language server won't - start under the hermetic PATH the test suite uses. Loading processx runs - `system("which uname")`, and R's `system()` goes through `popen`, which wants a `/bin/sh` - the sandbox doesn't provide. styler formats fine outside the sandbox. +- The R formatting test is pending too. styler produces the right answer — you can watch it + come back over the wire in the session log — but the R language server then never answers + lsp-test's `shutdown` and the session hangs rather than finishing. ## Known gaps diff --git a/tests/app/Spec/Tests/R.hs b/tests/app/Spec/Tests/R.hs index 41d28386..dc7d4ca8 100644 --- a/tests/app/Spec/Tests/R.hs +++ b/tests/app/Spec/Tests/R.hs @@ -36,11 +36,13 @@ tests = describe "R" $ introduceNixEnvironment [kernelSpec] [] "R" $ introduceJu VariableInspector.tests "R" - it "languageserver formats with styler" $ do - -- The R language server can't start under the hermetic PATH these tests use: loading - -- processx runs system("which uname"), and R's system() goes through popen, which needs - -- a /bin/sh the sandbox doesn't have. styler itself works -- the capability probe, which - -- runs outside the sandbox, formats fine. + it "languageserver formats test.R" $ do + -- styler produces the right answer here -- you can watch it come back over the wire in the + -- session log -- but the R language server then never answers lsp-test's `shutdown`, and + -- the session hangs instead of finishing. Driving the same sequence against the same + -- server in the same sandbox by hand shuts down fine, so it's something about how + -- lsp-test ends the session. Pending until that's sorted out; a hanging test is worse + -- than no test. _ <- pending formatsAs 180 "languageserver" "test.R" LanguageKind_R "f <- function(a,b){\na+b\n}\n" From 22e3ed6a0ecfaa842fc937ca2338f829c5e64b8c Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 20:15:02 -0700 Subject: [PATCH 18/34] Rust: point the pending formatting test at the upstream rnls fix --- FEATURE-MATRIX.md | 10 +++++----- tests/app/Spec/Tests/Rust/Formatting.hs | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/FEATURE-MATRIX.md b/FEATURE-MATRIX.md index 9816391e..086d433e 100644 --- a/FEATURE-MATRIX.md +++ b/FEATURE-MATRIX.md @@ -217,11 +217,11 @@ Caveats the table above can't show: the matrix. tinymist already formatted with typstyle by default; the setting exists to pick typstfmt or turn formatting off. - Rust formats correctly when rust-analyzer is driven directly, but - rust-notebook-language-server forwards `textDocument/formatting` with the notebook's URI - instead of the shadow file's, so rust-analyzer answers "file not found". Other requests - (`textDocument/documentSymbol`, …) are rewritten and work. That's a fix for the - [rust-notebook-language-server](https://github.com/codedownio/rust-notebook-language-server) - side, not this repo, so the Rust formatting test is marked pending. + rust-notebook-language-server forwarded `textDocument/formatting` with the notebook's URI + instead of the shadow file's, so rust-analyzer answered "file not found". + [rust-notebook-language-server#2](https://github.com/codedownio/rust-notebook-language-server/pull/2) + fixes it; the Rust formatting test stays pending until that's released and the pinned + version here moves up. - The R formatting test is pending too. styler produces the right answer — you can watch it come back over the wire in the session log — but the R language server then never answers lsp-test's `shutdown` and the session hangs rather than finishing. diff --git a/tests/app/Spec/Tests/Rust/Formatting.hs b/tests/app/Spec/Tests/Rust/Formatting.hs index 159bdc4f..ebc9dcd4 100644 --- a/tests/app/Spec/Tests/Rust/Formatting.hs +++ b/tests/app/Spec/Tests/Rust/Formatting.hs @@ -13,6 +13,9 @@ tests = describe "Formatting" $ -- rust-notebook-language-server forwards textDocument/formatting with the notebook's URI -- rather than the shadow file's, so rust-analyzer answers "file not found". Everything -- else is rewritten, which is why the rest of the LSP tests pass. + -- + -- https://github.com/codedownio/rust-notebook-language-server/pull/2 fixes it; this test + -- can come off pending once that's released and rnls-version.nix moves up. _ <- pending formatsAs 300 "rust-analyzer" "test.rs" LanguageKind_Rust "fn main(){let x = 1+2;println!(\"{}\",x);}\n" From 60aeb8f2918a9e08ad367fdccee24fab0371ffd7 Mon Sep 17 00:00:00 2001 From: Tom McLaughlin Date: Thu, 8 Oct 2026 23:15:01 -0700 Subject: [PATCH 19/34] Update Typst formatter description Simplified the description of the Typst formatter option. --- modules/exporters/typst/module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exporters/typst/module.nix b/modules/exporters/typst/module.nix index ef6f475b..47084ff9 100644 --- a/modules/exporters/typst/module.nix +++ b/modules/exporters/typst/module.nix @@ -57,7 +57,7 @@ in lsp.tinymist.formatter = mkOption { title = "Typst formatter"; - description = "Which formatter tinymist uses. Both are vendored into tinymist, so picking one costs nothing extra. typstyle is also tinymist's own default."; + description = "Which formatter tinymist uses."; type = types.enum ["typstyle" "typstfmt" "none"]; default = "typstyle"; }; From 9ca17ef3500c8e5c6d3bb4b93f62fbb3042d51ea Mon Sep 17 00:00:00 2001 From: Tom McLaughlin Date: Thu, 8 Oct 2026 23:16:18 -0700 Subject: [PATCH 20/34] Simplify gopls formatter description noci Removed extra details from gopls formatter description. --- modules/kernels/go/module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kernels/go/module.nix b/modules/kernels/go/module.nix index 020924b6..6c0f73bf 100644 --- a/modules/kernels/go/module.nix +++ b/modules/kernels/go/module.nix @@ -49,7 +49,7 @@ with lib; }; lsp.gopls.formatter = mkOption { title = "Go formatter"; - description = "Which formatter gopls uses. Both are built into gopls; gofumpt is gofmt plus a set of stricter rules."; + description = "Which formatter gopls uses."; type = types.enum ["gofmt" "gofumpt"]; default = "gofmt"; }; From e4e96e710241eb49ea1a3d9f37ce063ffde00f9e Mon Sep 17 00:00:00 2001 From: Tom McLaughlin Date: Thu, 8 Oct 2026 23:17:26 -0700 Subject: [PATCH 21/34] Simplify description for Haskell formatter option noci Removed extra information from the description of the Haskell formatter option. --- modules/kernels/haskell/module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kernels/haskell/module.nix b/modules/kernels/haskell/module.nix index 7c09950f..da7d98ab 100644 --- a/modules/kernels/haskell/module.nix +++ b/modules/kernels/haskell/module.nix @@ -50,7 +50,7 @@ in }; lsp.haskell-language-server.formattingProvider = mkOption { title = "Haskell formatter"; - description = "Which formatter haskell-language-server uses. All of these are compiled into it, so picking one costs nothing extra."; + description = "Which formatter haskell-language-server uses."; type = types.enum ["ormolu" "fourmolu" "stylish-haskell" "none"]; default = "ormolu"; }; From c27a283a9e4736b27d2a6d2ebea8a73e6956be1b Mon Sep 17 00:00:00 2001 From: Tom McLaughlin Date: Thu, 8 Oct 2026 23:19:13 -0700 Subject: [PATCH 22/34] Simplify description for python-lsp-server formatter Shortened the description for the Python formatter option in the lsp configuration. --- modules/kernels/python/module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kernels/python/module.nix b/modules/kernels/python/module.nix index 319dd002..86394c33 100644 --- a/modules/kernels/python/module.nix +++ b/modules/kernels/python/module.nix @@ -104,7 +104,7 @@ let }; lsp.python-lsp-server.formatter = mkOption { title = "Python formatter"; - description = "Which formatter python-lsp-server uses. It ships with none of them, so this also decides what gets added to the environment. autopep8 is the lightest and reuses the pycodestyle settings above; black and ruff reformat more aggressively. Note that Jedi, the default language server, can't format at all -- this only applies when python-lsp-server is enabled."; + description = "Which formatter python-lsp-server uses."; type = types.enum ["autopep8" "yapf" "black" "ruff" "none"]; default = "autopep8"; }; From 55b5aac7d0fb02c093bf8c4cb8a47cd8d3c60a80 Mon Sep 17 00:00:00 2001 From: Tom McLaughlin Date: Thu, 8 Oct 2026 23:19:57 -0700 Subject: [PATCH 23/34] Remove description for rubocop formatting option Removed description for Solargraph formatting option. --- modules/kernels/ruby/module.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/kernels/ruby/module.nix b/modules/kernels/ruby/module.nix index dd3706ae..da946692 100644 --- a/modules/kernels/ruby/module.nix +++ b/modules/kernels/ruby/module.nix @@ -63,7 +63,6 @@ in lsp.solargraph.formatting = mkOption { title = "Format Ruby code with rubocop"; - description = "Solargraph formats by running rubocop's autocorrect over the cell, using the configuration below. Turning this off also stops Solargraph from advertising formatting to the editor."; type = types.bool; default = true; }; From e2c9fb62977035344150d08da53567e2adfd0773 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Thu, 8 Oct 2026 23:31:58 -0700 Subject: [PATCH 24/34] Feature matrix: keep the Formatting column as plain check marks --- FEATURE-MATRIX.md | 29 ++- docs/feature-matrix-dark.svg | 272 ++++++++++----------- docs/feature-matrix.json | 2 +- docs/feature-matrix.svg | 272 ++++++++++----------- nix/feature-matrix.nix | 6 +- plans/inlay-hints-and-notebook-suffixes.md | 160 ++++++++++++ scripts/render-feature-matrix.py | 66 +---- 7 files changed, 457 insertions(+), 350 deletions(-) create mode 100644 plans/inlay-hints-and-notebook-suffixes.md diff --git a/FEATURE-MATRIX.md b/FEATURE-MATRIX.md index 086d433e..71f389b9 100644 --- a/FEATURE-MATRIX.md +++ b/FEATURE-MATRIX.md @@ -77,23 +77,23 @@ Which languages support which notebook and editor features. The graphic version | Language | Formatting | Rename symbol | Code actions | | --- | --- | --- | --- | -| Bash | ✅ shfmt | ✅ | ✅ | -| C++ 23 | ✅ clang-format | ✅ | ✅ | -| Clojure | ✅ cljfmt | ✅ | ✅ | +| Bash | ✅ | ✅ | ✅ | +| C++ 23 | ✅ | ✅ | ✅ | +| Clojure | ✅ | ✅ | ✅ | | Coq | – | – | – | -| Go | ✅ gofmt | ✅ | ✅ | -| Haskell | ✅ ormolu | ✅ | ✅ | -| JavaScript | ✅ tsserver | ✅ | ✅ | -| Julia | ✅ JuliaFormatter | ✅ | ✅ | +| Go | ✅ | ✅ | ✅ | +| Haskell | ✅ | ✅ | ✅ | +| JavaScript | ✅ | ✅ | ✅ | +| Julia | ✅ | ✅ | ✅ | | Octave | – | – | – | | PostgreSQL | – | – | – | | PyPy | – | ✅ | ✅ | | Python | – | ✅ | ✅ | -| R | ✅ styler | ✅ | ✅ | +| R | ✅ | ✅ | ✅ | | R (Ark) | – | – | – | -| Ruby | ✅ rubocop | ✅ | – | -| Rust | ✅ rustfmt | ✅ | ✅ | -| TypeScript | ✅ tsserver | ✅ | ✅ | +| Ruby | ✅ | ✅ | – | +| Rust | ✅ | ✅ | ✅ | +| TypeScript | ✅ | ✅ | ✅ | ### Packages @@ -169,9 +169,8 @@ by anything that wants to render this data elsewhere. Its shape: ``` `level` is `full`, `none`, or `unknown` (nothing probed that kernel yet), and `detail` says -which language server or REPL is behind it. A feature can also carry `"render": "label"`, -which asks the renderers to print `detail` in the cell rather than a check mark; `formatting` -is the one that does, so its cell names the formatter. +which language server or REPL is behind it — for `formatting`, the formatter. The SVG shows +`detail` in a cell's tooltip. ## Formatting @@ -181,7 +180,7 @@ tool behind it isn't installed — bash-language-server did exactly that until s to its wrapper. So the column is the conjunction of two things: the server advertises formatting, *and* the kernel names the formatter behind it in `passthru.formatters` (declared next to each language server's config and aggregated in the kernel's `default.nix`). That name -is what the cell prints. +is what the table below records, and what the SVG shows in the cell's tooltip. | Language | Formatter | Comes from | Setting | | --- | --- | --- | --- | diff --git a/docs/feature-matrix-dark.svg b/docs/feature-matrix-dark.svg index 166c2b4b..24d8aa3e 100644 --- a/docs/feature-matrix-dark.svg +++ b/docs/feature-matrix-dark.svg @@ -1,6 +1,6 @@ - + codedown language feature matrix - + Language feature support Generated from the codedown-languages Nix modules and live language server capabilities @@ -10,10 +10,10 @@ Code intelligence Navigation - - Editing - - Packages + + Editing + + Packages Jupyter kernel REPL Variable inspector @@ -32,11 +32,11 @@ Document outline Workspace symbol search Highlight occurrences - Formatting - Rename symbol - Code actions - Subpackage management - + Formatting + Rename symbol + Code actions + Subpackage management + Bash 5.3p9 Bash - Jupyter kernel: yes @@ -107,19 +107,19 @@ Bash - Formatting: yes (shfmt) - - shfmt + + Bash - Rename symbol: yes (bash-language-server) - - + + Bash - Code actions: yes (bash-language-server) - - + + Bash - Subpackage management: no - + C++ 23 21.1.8 @@ -194,21 +194,21 @@ C++ 23 - Formatting: yes (clang-format) - - clang-format + + C++ 23 - Rename symbol: yes (clangd) - - + + C++ 23 - Code actions: yes (clangd) - - + + C++ 23 - Subpackage management: no - + - + Clojure 1.12.5.1645 Clojure - Jupyter kernel: yes @@ -281,19 +281,19 @@ Clojure - Formatting: yes (cljfmt) - - cljfmt + + Clojure - Rename symbol: yes (clojure-lsp) - - + + Clojure - Code actions: yes (clojure-lsp) - - + + Clojure - Subpackage management: no - + Coq 9.1.1 @@ -355,19 +355,19 @@ Coq - Formatting: no - + Coq - Rename symbol: no - + Coq - Code actions: no - + Coq - Subpackage management: yes - - + + - + Go 1.26.6 Go - Jupyter kernel: yes @@ -440,19 +440,19 @@ Go - Formatting: yes (gofmt) - - gofmt + + Go - Rename symbol: yes (gopls) - - + + Go - Code actions: yes (gopls) - - + + Go - Subpackage management: no - + Haskell 9.10.3 @@ -527,22 +527,22 @@ Haskell - Formatting: yes (ormolu) - - ormolu + + Haskell - Rename symbol: yes (haskell-language-server) - - + + Haskell - Code actions: yes (haskell-language-server) - - + + Haskell - Subpackage management: yes - - + + - + JavaScript 24.19.0 JavaScript - Jupyter kernel: yes @@ -616,20 +616,20 @@ JavaScript - Formatting: yes (tsserver) - - tsserver + + JavaScript - Rename symbol: yes (typescript-language-server) - - + + JavaScript - Code actions: yes (typescript-language-server) - - + + JavaScript - Subpackage management: yes - - + + Julia 1.12.6 @@ -703,22 +703,22 @@ Julia - Formatting: yes (JuliaFormatter) - - JuliaFormatter + + Julia - Rename symbol: yes (LanguageServer) - - + + Julia - Code actions: yes (LanguageServer) - - + + Julia - Subpackage management: yes - - + + - + Octave 11.1.0 Octave - Jupyter kernel: yes @@ -780,17 +780,17 @@ Octave - Formatting: no - + Octave - Rename symbol: no - + Octave - Code actions: no - + Octave - Subpackage management: yes - - + + PostgreSQL 0.1 @@ -852,18 +852,18 @@ PostgreSQL - Formatting: no - + PostgreSQL - Rename symbol: no - + PostgreSQL - Code actions: no - + PostgreSQL - Subpackage management: no - + - + PyPy 7.3.20 PyPy - Jupyter kernel: yes @@ -937,19 +937,19 @@ PyPy - Formatting: no - + PyPy - Rename symbol: yes (jedi) - - + + PyPy - Code actions: yes (jedi) - - + + PyPy - Subpackage management: yes - - + + Python 3.13.15 @@ -1024,21 +1024,21 @@ Python - Formatting: no - + Python - Rename symbol: yes (jedi) - - + + Python - Code actions: yes (jedi) - - + + Python - Subpackage management: yes - - + + - + R 4.5.3 R - Jupyter kernel: yes @@ -1110,20 +1110,20 @@ R - Formatting: yes (styler) - - styler + + R - Rename symbol: yes (languageserver) - - + + R - Code actions: yes (languageserver) - - + + R - Subpackage management: yes - - + + R (Ark) 4.5.3 @@ -1187,19 +1187,19 @@ R (Ark) - Formatting: no - + R (Ark) - Rename symbol: no - + R (Ark) - Code actions: no - + R (Ark) - Subpackage management: yes - - + + - + Ruby 3.4.9 Ruby - Jupyter kernel: yes @@ -1272,19 +1272,19 @@ Ruby - Formatting: yes (rubocop) - - rubocop + + Ruby - Rename symbol: yes (solargraph) - - + + Ruby - Code actions: no - + Ruby - Subpackage management: yes - - + + Rust 1.95.0 @@ -1360,22 +1360,22 @@ Rust - Formatting: yes (rustfmt) - - rustfmt + + Rust - Rename symbol: yes (rust-analyzer) - - + + Rust - Code actions: yes (rust-analyzer) - - + + Rust - Subpackage management: yes - - + + - + TypeScript 24.19.0 TypeScript - Jupyter kernel: yes @@ -1449,25 +1449,25 @@ TypeScript - Formatting: yes (tsserver) - - tsserver + + TypeScript - Rename symbol: yes (typescript-language-server) - - + + TypeScript - Code actions: yes (typescript-language-server) - - + + TypeScript - Subpackage management: yes - - + + - + diff --git a/docs/feature-matrix.json b/docs/feature-matrix.json index bb91f26f..c64bf205 100644 --- a/docs/feature-matrix.json +++ b/docs/feature-matrix.json @@ -1 +1 @@ -{"features":[{"description":"Runs notebook cells through a Jupyter kernel.","group":"core","id":"jupyterKernel","name":"Jupyter kernel","source":"nix"},{"description":"Ships an interactive console you can attach to the same session.","group":"core","id":"repl","name":"REPL","source":"nix"},{"description":"Lists the variables currently defined in the session, with drill-down.","group":"core","id":"variableInspector","name":"Variable inspector","source":"nix"},{"description":"Breakpoints and stepping via the Jupyter debug protocol.","group":"core","id":"debugger","name":"Debugger","source":"nix"},{"description":"Editor highlighting for the language.","group":"core","id":"syntaxHighlighting","name":"Syntax highlighting","source":"nix"},{"description":"An LSP server is wired up for notebook cells and files.","group":"intelligence","id":"languageServer","name":"Language server","source":"nix"},{"description":"Context-aware completions as you type.","group":"intelligence","id":"completion","name":"Code completion","source":"lsp"},{"description":"Type and documentation popups on hover.","group":"intelligence","id":"hover","name":"Hover docs","source":"lsp"},{"description":"Parameter hints while calling a function.","group":"intelligence","id":"signatureHelp","name":"Signature help","source":"lsp"},{"description":"Errors, warnings and lints reported inline.","group":"intelligence","id":"diagnostics","name":"Diagnostics","source":"lsp"},{"description":"Highlighting driven by the compiler/analyzer rather than a regex grammar.","group":"intelligence","id":"semanticTokens","name":"Semantic highlighting","source":"lsp"},{"description":"Inferred types and parameter names rendered inline.","group":"intelligence","id":"inlayHints","name":"Inlay hints","source":"lsp"},{"description":"Go from a use of a symbol to where it's defined.","group":"navigation","id":"definition","name":"Jump to definition","source":"lsp"},{"description":"Go from a value to the definition of its type.","group":"navigation","id":"typeDefinition","name":"Jump to type definition","source":"lsp"},{"description":"Find every use of a symbol.","group":"navigation","id":"references","name":"Find references","source":"lsp"},{"description":"Symbol tree for the current document.","group":"navigation","id":"documentSymbols","name":"Document outline","source":"lsp"},{"description":"Search symbols across the whole project.","group":"navigation","id":"workspaceSymbols","name":"Workspace symbol search","source":"lsp"},{"description":"Highlights other occurrences of the symbol under the cursor.","group":"navigation","id":"documentHighlight","name":"Highlight occurrences","source":"lsp"},{"description":"Format a document or a selection. The cell names the formatter behind it.","group":"editing","id":"formatting","name":"Formatting","render":"label","source":"both"},{"description":"Rename a symbol and every reference to it.","group":"editing","id":"rename","name":"Rename symbol","source":"lsp"},{"description":"Quick fixes and refactorings offered on a range.","group":"editing","id":"codeActions","name":"Code actions","source":"lsp"},{"description":"A searchable set of language packages you can add to the environment declaratively.","group":"packages","id":"subPackageManagement","name":"Subpackage management","source":"nix"}],"groups":[{"id":"core","name":"Notebook"},{"id":"intelligence","name":"Code intelligence"},{"id":"navigation","name":"Navigation"},{"id":"editing","name":"Editing"},{"id":"packages","name":"Packages"}],"languages":[{"attrs":["bash"],"componentVersions":{"bash":"5.3p9","bash-kernel":"0.10.0","bash-language-server":"5.6.0"},"description":"GNU Bourne-Again Shell, the de facto standard shell on Linux (for interactive use)","displayName":"Bash","extensions":["sh","bash"],"iconMonochrome":"modules/kernels/bash/gnubash-monochrome.svg","id":"bash","languageServers":{"available":["bash-language-server"],"enabledByDefault":["bash-language-server"],"probed":["bash-language-server"]},"support":{"codeActions":{"detail":"bash-language-server","level":"full"},"completion":{"detail":"bash-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"bash-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"bash-language-server","level":"full"},"documentSymbols":{"detail":"bash-language-server","level":"full"},"formatting":{"detail":"shfmt","level":"full"},"hover":{"detail":"bash-language-server","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"bash-language-server","level":"full"},"references":{"detail":"bash-language-server","level":"full"},"rename":{"detail":"bash-language-server","level":"full"},"repl":{"detail":"bash","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"shell","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"bash-language-server","level":"full"}},"version":"5.3p9"},{"attrs":["c++23","cpp"],"componentVersions":{"clang":"21.1.8","clangd":"21.1.8","cling":"1.2","std":"c++23","xeus-cling":"0.15.3"},"description":"C language family frontend for LLVM (wrapper script)","displayName":"C++ 23","extensions":["cpp","hpp","cxx","hxx","c","h"],"iconMonochrome":"modules/kernels/cpp/cpp23-monochrome.svg","id":"cpp","languageServers":{"available":["clangd"],"enabledByDefault":["clangd"],"probed":["clangd"]},"support":{"codeActions":{"detail":"clangd","level":"full"},"completion":{"detail":"clangd","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"clangd","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"clangd","level":"full"},"documentSymbols":{"detail":"clangd","level":"full"},"formatting":{"detail":"clang-format","level":"full"},"hover":{"detail":"clangd","level":"full"},"inlayHints":{"detail":"clangd","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"clangd","level":"full"},"references":{"detail":"clangd","level":"full"},"rename":{"detail":"clangd","level":"full"},"repl":{"detail":"cling","level":"full"},"semanticTokens":{"detail":"clangd","level":"full"},"signatureHelp":{"detail":"clangd","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"clike","level":"full"},"typeDefinition":{"detail":"clangd","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"clangd","level":"full"}},"version":"21.1.8"},{"attrs":["clojure"],"componentVersions":{"clojure":"1.12.5.1645","clojure-lsp":"2026.02.20-16.08.58"},"description":"Lisp dialect for the JVM","displayName":"Clojure","extensions":["clj"],"iconMonochrome":"modules/kernels/clojure/clojure-monochrome.svg","id":"clojure","languageServers":{"available":["clojure-lsp"],"enabledByDefault":["clojure-lsp"],"probed":["clojure-lsp"]},"support":{"codeActions":{"detail":"clojure-lsp","level":"full"},"completion":{"detail":"clojure-lsp","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"clojure-lsp","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"clojure-lsp","level":"full"},"documentSymbols":{"detail":"clojure-lsp","level":"full"},"formatting":{"detail":"cljfmt","level":"full"},"hover":{"detail":"clojure-lsp","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"clojure-lsp","level":"full"},"references":{"detail":"clojure-lsp","level":"full"},"rename":{"detail":"clojure-lsp","level":"full"},"repl":{"detail":"clojure","level":"full"},"semanticTokens":{"detail":"clojure-lsp","level":"full"},"signatureHelp":{"detail":"clojure-lsp","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"clojure","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"clojure-lsp","level":"full"}},"version":"1.12.5.1645"},{"attrs":["coq"],"componentVersions":{"coq":"9.1.1"},"description":"Coq proof assistant","displayName":"Coq","extensions":["v"],"iconMonochrome":"modules/kernels/coq/coq-monochrome.svg","id":"coq","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"coq","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"9.1.1"},{"attrs":["go"],"componentVersions":{"go":"1.26.6","gopls":"0.22.0"},"description":"Go Programming language","displayName":"Go","extensions":["go"],"iconMonochrome":"modules/kernels/go/go-monochrome.svg","id":"go","languageServers":{"available":["gopls"],"enabledByDefault":["gopls"],"probed":["gopls"]},"support":{"codeActions":{"detail":"gopls","level":"full"},"completion":{"detail":"gopls","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"gopls","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"gopls","level":"full"},"documentSymbols":{"detail":"gopls","level":"full"},"formatting":{"detail":"gofmt","level":"full"},"hover":{"detail":"gopls","level":"full"},"inlayHints":{"detail":"gopls","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"gopls","level":"full"},"references":{"detail":"gopls","level":"full"},"rename":{"detail":"gopls","level":"full"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"gopls","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"go","level":"full"},"typeDefinition":{"detail":"gopls","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"gopls","level":"full"}},"version":"1.26.6"},{"attrs":["haskell"],"componentVersions":{"ghc":"9.10.3","haskell-language-server":"2.13.0.0"},"description":"An advanced, purely functional programming language (GHC 9.10.3)","displayName":"Haskell","extensions":["hs"],"iconMonochrome":"modules/kernels/haskell/haskell-monochrome.svg","id":"haskell","languageServers":{"available":["haskell-language-server"],"enabledByDefault":["haskell-language-server"],"probed":["haskell-language-server"]},"support":{"codeActions":{"detail":"haskell-language-server","level":"full"},"completion":{"detail":"haskell-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"haskell-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"haskell-language-server","level":"full"},"documentSymbols":{"detail":"haskell-language-server","level":"full"},"formatting":{"detail":"ormolu","level":"full"},"hover":{"detail":"haskell-language-server","level":"full"},"inlayHints":{"detail":"haskell-language-server","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"haskell-language-server","level":"full"},"references":{"detail":"haskell-language-server","level":"full"},"rename":{"detail":"haskell-language-server","level":"full"},"repl":{"detail":"ghci","level":"full"},"semanticTokens":{"detail":"haskell-language-server","level":"full"},"signatureHelp":{"detail":"haskell-language-server","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"haskell","level":"full"},"typeDefinition":{"detail":"haskell-language-server","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"haskell-language-server","level":"full"}},"version":"9.10.3"},{"attrs":["javascript","js"],"componentVersions":{"nodejs":"24.19.0","tslab":"1.0.22"},"description":"Event-driven I/O framework for the V8 JavaScript engine","displayName":"JavaScript","extensions":["js","mjs","cjs"],"iconMonochrome":"modules/kernels/javascript/javascript-monochrome.svg","id":"javascript","languageServers":{"available":["typescript-language-server"],"enabledByDefault":["typescript-language-server"],"probed":["typescript-language-server"]},"support":{"codeActions":{"detail":"typescript-language-server","level":"full"},"completion":{"detail":"typescript-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"typescript-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"typescript-language-server","level":"full"},"documentSymbols":{"detail":"typescript-language-server","level":"full"},"formatting":{"detail":"tsserver","level":"full"},"hover":{"detail":"typescript-language-server","level":"full"},"inlayHints":{"detail":"typescript-language-server","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"typescript-language-server","level":"full"},"references":{"detail":"typescript-language-server","level":"full"},"rename":{"detail":"typescript-language-server","level":"full"},"repl":{"detail":"node","level":"full"},"semanticTokens":{"detail":"typescript-language-server","level":"full"},"signatureHelp":{"detail":"typescript-language-server","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"javascript","level":"full"},"typeDefinition":{"detail":"typescript-language-server","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"typescript-language-server","level":"full"}},"version":"24.19.0"},{"attrs":["julia"],"componentVersions":{"julia":"1.12.6"},"description":"High-level performance-oriented dynamical language for technical computing","displayName":"Julia","extensions":["jl"],"iconMonochrome":"modules/kernels/julia/julia-monochrome.svg","id":"julia","languageServers":{"available":["LanguageServer"],"enabledByDefault":["LanguageServer"],"probed":["LanguageServer"]},"support":{"codeActions":{"detail":"LanguageServer","level":"full"},"completion":{"detail":"LanguageServer","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"LanguageServer","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"LanguageServer","level":"full"},"documentSymbols":{"detail":"LanguageServer","level":"full"},"formatting":{"detail":"JuliaFormatter","level":"full"},"hover":{"detail":"LanguageServer","level":"full"},"inlayHints":{"detail":"LanguageServer","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"LanguageServer","level":"full"},"references":{"detail":"LanguageServer","level":"full"},"rename":{"detail":"LanguageServer","level":"full"},"repl":{"detail":"julia","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"LanguageServer","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"julia","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"LanguageServer","level":"full"}},"version":"1.12.6"},{"attrs":["octave"],"componentVersions":{"octave":"11.1.0"},"description":"Scientific Programming Language","displayName":"Octave","extensions":["m"],"iconMonochrome":"modules/kernels/octave/octave-monochrome.svg","id":"octave","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"octave","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"octave","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"11.1.0"},{"attrs":["postgres"],"componentVersions":{"postgres-kernel":"0.1"},"description":"A simple Jupyter kernel for PostgreSQL","displayName":"PostgreSQL","extensions":["sql"],"iconMonochrome":"modules/kernels/postgres/postgresql-monochrome.svg","id":"postgres","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"sql","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"0.1"},{"attrs":["python3","python"],"componentVersions":{"flake8":"7.3.0","jedi-language-server":"0.46.0","pycodestyle":"2.14.0","pylint":"4.0.5","pyright":"1.1.411","python":"7.3.20","python-lsp-server":"1.14.0"},"description":"Fast, compliant alternative implementation of the Python language (3.11)","displayName":"PyPy","extensions":["py"],"iconMonochrome":"modules/kernels/python/python-monochrome.svg","id":"pypy3","languageServers":{"available":["flake8","jedi","microsoft","pycodestyle","pylint","pyright","python-language-server","python-lsp-server"],"enabledByDefault":["jedi"],"probed":["jedi"]},"support":{"codeActions":{"detail":"jedi","level":"full"},"completion":{"detail":"jedi","level":"full"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":"jedi","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"jedi","level":"full"},"documentSymbols":{"detail":"jedi","level":"full"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":"jedi","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"jedi","level":"full"},"references":{"detail":"jedi","level":"full"},"rename":{"detail":"jedi","level":"full"},"repl":{"detail":"ipython","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"jedi","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"python","level":"full"},"typeDefinition":{"detail":"jedi","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"jedi","level":"full"}},"version":"7.3.20"},{"attrs":["python3","python"],"componentVersions":{"flake8":"7.3.0","jedi-language-server":"0.46.0","pycodestyle":"2.14.0","pylint":"4.0.5","pyright":"1.1.411","python":"3.13.15","python-lsp-server":"1.14.0"},"description":"High-level dynamically-typed programming language","displayName":"Python","extensions":["py"],"iconMonochrome":"modules/kernels/python/python-monochrome.svg","id":"python3","languageServers":{"available":["flake8","jedi","microsoft","pycodestyle","pylint","pyright","python-language-server","python-lsp-server"],"enabledByDefault":["jedi"],"probed":["jedi"]},"support":{"codeActions":{"detail":"jedi","level":"full"},"completion":{"detail":"jedi","level":"full"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":"jedi","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"jedi","level":"full"},"documentSymbols":{"detail":"jedi","level":"full"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":"jedi","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"jedi","level":"full"},"references":{"detail":"jedi","level":"full"},"rename":{"detail":"jedi","level":"full"},"repl":{"detail":"ipython","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"jedi","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"python","level":"full"},"typeDefinition":{"detail":"jedi","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"jedi","level":"full"}},"version":"3.13.15"},{"attrs":["r","R"],"componentVersions":{"languageserver":"0.3.16","r":"4.5.3"},"description":"Free software environment for statistical computing and graphics","displayName":"R","extensions":["r"],"iconMonochrome":"modules/kernels/r/r-monochrome.svg","id":"R","languageServers":{"available":["languageserver"],"enabledByDefault":["languageserver"],"probed":["languageserver"]},"support":{"codeActions":{"detail":"languageserver","level":"full"},"completion":{"detail":"languageserver","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"languageserver","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"languageserver","level":"full"},"documentSymbols":{"detail":"languageserver","level":"full"},"formatting":{"detail":"styler","level":"full"},"hover":{"detail":"languageserver","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"languageserver","level":"full"},"references":{"detail":"languageserver","level":"full"},"rename":{"detail":"languageserver","level":"full"},"repl":{"detail":"r","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"languageserver","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"r","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"languageserver","level":"full"}},"version":"4.5.3"},{"attrs":["r","R"],"componentVersions":{"ark":"0.1.252","r":"4.5.3"},"description":"Free software environment for statistical computing and graphics","displayName":"R (Ark)","extensions":["r"],"iconMonochrome":"modules/kernels/r-ark/r-monochrome.svg","id":"R-ark","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"r","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"r","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"4.5.3"},{"attrs":["ruby"],"componentVersions":{"iruby":"v0.8.2","ruby":"3.4.9","solargraph":"0.57.0"},"description":"Object-oriented language for quick and easy programming","displayName":"Ruby","extensions":["rb"],"iconMonochrome":"modules/kernels/ruby/ruby-monochrome.svg","id":"ruby","languageServers":{"available":["solargraph"],"enabledByDefault":["solargraph"],"probed":["solargraph"]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":"solargraph","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"solargraph","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"solargraph","level":"full"},"documentSymbols":{"detail":"solargraph","level":"full"},"formatting":{"detail":"rubocop","level":"full"},"hover":{"detail":"solargraph","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"solargraph","level":"full"},"references":{"detail":"solargraph","level":"full"},"rename":{"detail":"solargraph","level":"full"},"repl":{"detail":"irb","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"solargraph","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"ruby","level":"full"},"typeDefinition":{"detail":"solargraph","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"solargraph","level":"full"}},"version":"3.4.9"},{"attrs":["rust"],"componentVersions":{"cargo":"1.95.0","evcxr":"0.21.1","rust":"1.95.0","rust-analyzer":"2026-06-01"},"description":"Safe, concurrent, practical language (wrapper script)","displayName":"Rust","extensions":["rs","rlib"],"iconMonochrome":"modules/kernels/rust/rust-logo-monochrome.svg","id":"rust","languageServers":{"available":["rust-analyzer"],"enabledByDefault":["rust-analyzer"],"probed":["rust-analyzer"]},"support":{"codeActions":{"detail":"rust-analyzer","level":"full"},"completion":{"detail":"rust-analyzer","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"rust-analyzer","level":"full"},"diagnostics":{"detail":"pull + push","level":"full"},"documentHighlight":{"detail":"rust-analyzer","level":"full"},"documentSymbols":{"detail":"rust-analyzer","level":"full"},"formatting":{"detail":"rustfmt","level":"full"},"hover":{"detail":"rust-analyzer","level":"full"},"inlayHints":{"detail":"rust-analyzer","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"rust-analyzer","level":"full"},"references":{"detail":"rust-analyzer","level":"full"},"rename":{"detail":"rust-analyzer","level":"full"},"repl":{"detail":"evcxr","level":"full"},"semanticTokens":{"detail":"rust-analyzer","level":"full"},"signatureHelp":{"detail":"rust-analyzer","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"rust","level":"full"},"typeDefinition":{"detail":"rust-analyzer","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"rust-analyzer","level":"full"}},"version":"1.95.0"},{"attrs":["typescript","ts"],"componentVersions":{"nodejs":"24.19.0","tslab":"1.0.22"},"description":"Event-driven I/O framework for the V8 JavaScript engine","displayName":"TypeScript","extensions":["ts","tsx"],"iconMonochrome":"modules/kernels/typescript/typescript-monochrome.svg","id":"typescript","languageServers":{"available":["typescript-language-server"],"enabledByDefault":["typescript-language-server"],"probed":["typescript-language-server"]},"support":{"codeActions":{"detail":"typescript-language-server","level":"full"},"completion":{"detail":"typescript-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"typescript-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"typescript-language-server","level":"full"},"documentSymbols":{"detail":"typescript-language-server","level":"full"},"formatting":{"detail":"tsserver","level":"full"},"hover":{"detail":"typescript-language-server","level":"full"},"inlayHints":{"detail":"typescript-language-server","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"typescript-language-server","level":"full"},"references":{"detail":"typescript-language-server","level":"full"},"rename":{"detail":"typescript-language-server","level":"full"},"repl":{"detail":"node","level":"full"},"semanticTokens":{"detail":"typescript-language-server","level":"full"},"signatureHelp":{"detail":"typescript-language-server","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"javascript","level":"full"},"typeDefinition":{"detail":"typescript-language-server","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"typescript-language-server","level":"full"}},"version":"24.19.0"}],"provenance":{"lsp":"Read from language server `initialize` responses (nix/lsp-capabilities.json, regenerated by scripts/probe-lsp-capabilities).","lspProbedKernels":["R","R-ark","bash","clojure","coq","cpp","go","haskell","javascript","julia","octave","postgres","pypy3","python3","ruby","rust","typescript"],"nix":"Evaluated from the codedown-languages module system (nix/feature-matrix.nix)."},"schemaVersion":1} \ No newline at end of file +{"features":[{"description":"Runs notebook cells through a Jupyter kernel.","group":"core","id":"jupyterKernel","name":"Jupyter kernel","source":"nix"},{"description":"Ships an interactive console you can attach to the same session.","group":"core","id":"repl","name":"REPL","source":"nix"},{"description":"Lists the variables currently defined in the session, with drill-down.","group":"core","id":"variableInspector","name":"Variable inspector","source":"nix"},{"description":"Breakpoints and stepping via the Jupyter debug protocol.","group":"core","id":"debugger","name":"Debugger","source":"nix"},{"description":"Editor highlighting for the language.","group":"core","id":"syntaxHighlighting","name":"Syntax highlighting","source":"nix"},{"description":"An LSP server is wired up for notebook cells and files.","group":"intelligence","id":"languageServer","name":"Language server","source":"nix"},{"description":"Context-aware completions as you type.","group":"intelligence","id":"completion","name":"Code completion","source":"lsp"},{"description":"Type and documentation popups on hover.","group":"intelligence","id":"hover","name":"Hover docs","source":"lsp"},{"description":"Parameter hints while calling a function.","group":"intelligence","id":"signatureHelp","name":"Signature help","source":"lsp"},{"description":"Errors, warnings and lints reported inline.","group":"intelligence","id":"diagnostics","name":"Diagnostics","source":"lsp"},{"description":"Highlighting driven by the compiler/analyzer rather than a regex grammar.","group":"intelligence","id":"semanticTokens","name":"Semantic highlighting","source":"lsp"},{"description":"Inferred types and parameter names rendered inline.","group":"intelligence","id":"inlayHints","name":"Inlay hints","source":"lsp"},{"description":"Go from a use of a symbol to where it's defined.","group":"navigation","id":"definition","name":"Jump to definition","source":"lsp"},{"description":"Go from a value to the definition of its type.","group":"navigation","id":"typeDefinition","name":"Jump to type definition","source":"lsp"},{"description":"Find every use of a symbol.","group":"navigation","id":"references","name":"Find references","source":"lsp"},{"description":"Symbol tree for the current document.","group":"navigation","id":"documentSymbols","name":"Document outline","source":"lsp"},{"description":"Search symbols across the whole project.","group":"navigation","id":"workspaceSymbols","name":"Workspace symbol search","source":"lsp"},{"description":"Highlights other occurrences of the symbol under the cursor.","group":"navigation","id":"documentHighlight","name":"Highlight occurrences","source":"lsp"},{"description":"Format a document or a selection.","group":"editing","id":"formatting","name":"Formatting","source":"both"},{"description":"Rename a symbol and every reference to it.","group":"editing","id":"rename","name":"Rename symbol","source":"lsp"},{"description":"Quick fixes and refactorings offered on a range.","group":"editing","id":"codeActions","name":"Code actions","source":"lsp"},{"description":"A searchable set of language packages you can add to the environment declaratively.","group":"packages","id":"subPackageManagement","name":"Subpackage management","source":"nix"}],"groups":[{"id":"core","name":"Notebook"},{"id":"intelligence","name":"Code intelligence"},{"id":"navigation","name":"Navigation"},{"id":"editing","name":"Editing"},{"id":"packages","name":"Packages"}],"languages":[{"attrs":["bash"],"componentVersions":{"bash":"5.3p9","bash-kernel":"0.10.0","bash-language-server":"5.6.0"},"description":"GNU Bourne-Again Shell, the de facto standard shell on Linux (for interactive use)","displayName":"Bash","extensions":["sh","bash"],"iconMonochrome":"modules/kernels/bash/gnubash-monochrome.svg","id":"bash","languageServers":{"available":["bash-language-server"],"enabledByDefault":["bash-language-server"],"probed":["bash-language-server"]},"support":{"codeActions":{"detail":"bash-language-server","level":"full"},"completion":{"detail":"bash-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"bash-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"bash-language-server","level":"full"},"documentSymbols":{"detail":"bash-language-server","level":"full"},"formatting":{"detail":"shfmt","level":"full"},"hover":{"detail":"bash-language-server","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"bash-language-server","level":"full"},"references":{"detail":"bash-language-server","level":"full"},"rename":{"detail":"bash-language-server","level":"full"},"repl":{"detail":"bash","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"shell","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"bash-language-server","level":"full"}},"version":"5.3p9"},{"attrs":["c++23","cpp"],"componentVersions":{"clang":"21.1.8","clangd":"21.1.8","cling":"1.2","std":"c++23","xeus-cling":"0.15.3"},"description":"C language family frontend for LLVM (wrapper script)","displayName":"C++ 23","extensions":["cpp","hpp","cxx","hxx","c","h"],"iconMonochrome":"modules/kernels/cpp/cpp23-monochrome.svg","id":"cpp","languageServers":{"available":["clangd"],"enabledByDefault":["clangd"],"probed":["clangd"]},"support":{"codeActions":{"detail":"clangd","level":"full"},"completion":{"detail":"clangd","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"clangd","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"clangd","level":"full"},"documentSymbols":{"detail":"clangd","level":"full"},"formatting":{"detail":"clang-format","level":"full"},"hover":{"detail":"clangd","level":"full"},"inlayHints":{"detail":"clangd","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"clangd","level":"full"},"references":{"detail":"clangd","level":"full"},"rename":{"detail":"clangd","level":"full"},"repl":{"detail":"cling","level":"full"},"semanticTokens":{"detail":"clangd","level":"full"},"signatureHelp":{"detail":"clangd","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"clike","level":"full"},"typeDefinition":{"detail":"clangd","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"clangd","level":"full"}},"version":"21.1.8"},{"attrs":["clojure"],"componentVersions":{"clojure":"1.12.5.1645","clojure-lsp":"2026.02.20-16.08.58"},"description":"Lisp dialect for the JVM","displayName":"Clojure","extensions":["clj"],"iconMonochrome":"modules/kernels/clojure/clojure-monochrome.svg","id":"clojure","languageServers":{"available":["clojure-lsp"],"enabledByDefault":["clojure-lsp"],"probed":["clojure-lsp"]},"support":{"codeActions":{"detail":"clojure-lsp","level":"full"},"completion":{"detail":"clojure-lsp","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"clojure-lsp","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"clojure-lsp","level":"full"},"documentSymbols":{"detail":"clojure-lsp","level":"full"},"formatting":{"detail":"cljfmt","level":"full"},"hover":{"detail":"clojure-lsp","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"clojure-lsp","level":"full"},"references":{"detail":"clojure-lsp","level":"full"},"rename":{"detail":"clojure-lsp","level":"full"},"repl":{"detail":"clojure","level":"full"},"semanticTokens":{"detail":"clojure-lsp","level":"full"},"signatureHelp":{"detail":"clojure-lsp","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"clojure","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"clojure-lsp","level":"full"}},"version":"1.12.5.1645"},{"attrs":["coq"],"componentVersions":{"coq":"9.1.1"},"description":"Coq proof assistant","displayName":"Coq","extensions":["v"],"iconMonochrome":"modules/kernels/coq/coq-monochrome.svg","id":"coq","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"coq","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"9.1.1"},{"attrs":["go"],"componentVersions":{"go":"1.26.6","gopls":"0.22.0"},"description":"Go Programming language","displayName":"Go","extensions":["go"],"iconMonochrome":"modules/kernels/go/go-monochrome.svg","id":"go","languageServers":{"available":["gopls"],"enabledByDefault":["gopls"],"probed":["gopls"]},"support":{"codeActions":{"detail":"gopls","level":"full"},"completion":{"detail":"gopls","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"gopls","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"gopls","level":"full"},"documentSymbols":{"detail":"gopls","level":"full"},"formatting":{"detail":"gofmt","level":"full"},"hover":{"detail":"gopls","level":"full"},"inlayHints":{"detail":"gopls","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"gopls","level":"full"},"references":{"detail":"gopls","level":"full"},"rename":{"detail":"gopls","level":"full"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"gopls","level":"full"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"go","level":"full"},"typeDefinition":{"detail":"gopls","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"gopls","level":"full"}},"version":"1.26.6"},{"attrs":["haskell"],"componentVersions":{"ghc":"9.10.3","haskell-language-server":"2.13.0.0"},"description":"An advanced, purely functional programming language (GHC 9.10.3)","displayName":"Haskell","extensions":["hs"],"iconMonochrome":"modules/kernels/haskell/haskell-monochrome.svg","id":"haskell","languageServers":{"available":["haskell-language-server"],"enabledByDefault":["haskell-language-server"],"probed":["haskell-language-server"]},"support":{"codeActions":{"detail":"haskell-language-server","level":"full"},"completion":{"detail":"haskell-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"haskell-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"haskell-language-server","level":"full"},"documentSymbols":{"detail":"haskell-language-server","level":"full"},"formatting":{"detail":"ormolu","level":"full"},"hover":{"detail":"haskell-language-server","level":"full"},"inlayHints":{"detail":"haskell-language-server","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"haskell-language-server","level":"full"},"references":{"detail":"haskell-language-server","level":"full"},"rename":{"detail":"haskell-language-server","level":"full"},"repl":{"detail":"ghci","level":"full"},"semanticTokens":{"detail":"haskell-language-server","level":"full"},"signatureHelp":{"detail":"haskell-language-server","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"haskell","level":"full"},"typeDefinition":{"detail":"haskell-language-server","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"haskell-language-server","level":"full"}},"version":"9.10.3"},{"attrs":["javascript","js"],"componentVersions":{"nodejs":"24.19.0","tslab":"1.0.22"},"description":"Event-driven I/O framework for the V8 JavaScript engine","displayName":"JavaScript","extensions":["js","mjs","cjs"],"iconMonochrome":"modules/kernels/javascript/javascript-monochrome.svg","id":"javascript","languageServers":{"available":["typescript-language-server"],"enabledByDefault":["typescript-language-server"],"probed":["typescript-language-server"]},"support":{"codeActions":{"detail":"typescript-language-server","level":"full"},"completion":{"detail":"typescript-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"typescript-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"typescript-language-server","level":"full"},"documentSymbols":{"detail":"typescript-language-server","level":"full"},"formatting":{"detail":"tsserver","level":"full"},"hover":{"detail":"typescript-language-server","level":"full"},"inlayHints":{"detail":"typescript-language-server","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"typescript-language-server","level":"full"},"references":{"detail":"typescript-language-server","level":"full"},"rename":{"detail":"typescript-language-server","level":"full"},"repl":{"detail":"node","level":"full"},"semanticTokens":{"detail":"typescript-language-server","level":"full"},"signatureHelp":{"detail":"typescript-language-server","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"javascript","level":"full"},"typeDefinition":{"detail":"typescript-language-server","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"typescript-language-server","level":"full"}},"version":"24.19.0"},{"attrs":["julia"],"componentVersions":{"julia":"1.12.6"},"description":"High-level performance-oriented dynamical language for technical computing","displayName":"Julia","extensions":["jl"],"iconMonochrome":"modules/kernels/julia/julia-monochrome.svg","id":"julia","languageServers":{"available":["LanguageServer"],"enabledByDefault":["LanguageServer"],"probed":["LanguageServer"]},"support":{"codeActions":{"detail":"LanguageServer","level":"full"},"completion":{"detail":"LanguageServer","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"LanguageServer","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"LanguageServer","level":"full"},"documentSymbols":{"detail":"LanguageServer","level":"full"},"formatting":{"detail":"JuliaFormatter","level":"full"},"hover":{"detail":"LanguageServer","level":"full"},"inlayHints":{"detail":"LanguageServer","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"LanguageServer","level":"full"},"references":{"detail":"LanguageServer","level":"full"},"rename":{"detail":"LanguageServer","level":"full"},"repl":{"detail":"julia","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"LanguageServer","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"julia","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"LanguageServer","level":"full"}},"version":"1.12.6"},{"attrs":["octave"],"componentVersions":{"octave":"11.1.0"},"description":"Scientific Programming Language","displayName":"Octave","extensions":["m"],"iconMonochrome":"modules/kernels/octave/octave-monochrome.svg","id":"octave","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"octave","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"octave","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"11.1.0"},{"attrs":["postgres"],"componentVersions":{"postgres-kernel":"0.1"},"description":"A simple Jupyter kernel for PostgreSQL","displayName":"PostgreSQL","extensions":["sql"],"iconMonochrome":"modules/kernels/postgres/postgresql-monochrome.svg","id":"postgres","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"console","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"none"},"syntaxHighlighting":{"detail":"sql","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"0.1"},{"attrs":["python3","python"],"componentVersions":{"flake8":"7.3.0","jedi-language-server":"0.46.0","pycodestyle":"2.14.0","pylint":"4.0.5","pyright":"1.1.411","python":"7.3.20","python-lsp-server":"1.14.0"},"description":"Fast, compliant alternative implementation of the Python language (3.11)","displayName":"PyPy","extensions":["py"],"iconMonochrome":"modules/kernels/python/python-monochrome.svg","id":"pypy3","languageServers":{"available":["flake8","jedi","microsoft","pycodestyle","pylint","pyright","python-language-server","python-lsp-server"],"enabledByDefault":["jedi"],"probed":["jedi"]},"support":{"codeActions":{"detail":"jedi","level":"full"},"completion":{"detail":"jedi","level":"full"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":"jedi","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"jedi","level":"full"},"documentSymbols":{"detail":"jedi","level":"full"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":"jedi","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"jedi","level":"full"},"references":{"detail":"jedi","level":"full"},"rename":{"detail":"jedi","level":"full"},"repl":{"detail":"ipython","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"jedi","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"python","level":"full"},"typeDefinition":{"detail":"jedi","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"jedi","level":"full"}},"version":"7.3.20"},{"attrs":["python3","python"],"componentVersions":{"flake8":"7.3.0","jedi-language-server":"0.46.0","pycodestyle":"2.14.0","pylint":"4.0.5","pyright":"1.1.411","python":"3.13.15","python-lsp-server":"1.14.0"},"description":"High-level dynamically-typed programming language","displayName":"Python","extensions":["py"],"iconMonochrome":"modules/kernels/python/python-monochrome.svg","id":"python3","languageServers":{"available":["flake8","jedi","microsoft","pycodestyle","pylint","pyright","python-language-server","python-lsp-server"],"enabledByDefault":["jedi"],"probed":["jedi"]},"support":{"codeActions":{"detail":"jedi","level":"full"},"completion":{"detail":"jedi","level":"full"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":"jedi","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"jedi","level":"full"},"documentSymbols":{"detail":"jedi","level":"full"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":"jedi","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"jedi","level":"full"},"references":{"detail":"jedi","level":"full"},"rename":{"detail":"jedi","level":"full"},"repl":{"detail":"ipython","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"jedi","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"python","level":"full"},"typeDefinition":{"detail":"jedi","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"jedi","level":"full"}},"version":"3.13.15"},{"attrs":["r","R"],"componentVersions":{"languageserver":"0.3.16","r":"4.5.3"},"description":"Free software environment for statistical computing and graphics","displayName":"R","extensions":["r"],"iconMonochrome":"modules/kernels/r/r-monochrome.svg","id":"R","languageServers":{"available":["languageserver"],"enabledByDefault":["languageserver"],"probed":["languageserver"]},"support":{"codeActions":{"detail":"languageserver","level":"full"},"completion":{"detail":"languageserver","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"languageserver","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"languageserver","level":"full"},"documentSymbols":{"detail":"languageserver","level":"full"},"formatting":{"detail":"styler","level":"full"},"hover":{"detail":"languageserver","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"languageserver","level":"full"},"references":{"detail":"languageserver","level":"full"},"rename":{"detail":"languageserver","level":"full"},"repl":{"detail":"r","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"languageserver","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"r","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"languageserver","level":"full"}},"version":"4.5.3"},{"attrs":["r","R"],"componentVersions":{"ark":"0.1.252","r":"4.5.3"},"description":"Free software environment for statistical computing and graphics","displayName":"R (Ark)","extensions":["r"],"iconMonochrome":"modules/kernels/r-ark/r-monochrome.svg","id":"R-ark","languageServers":{"available":[],"enabledByDefault":[],"probed":[]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":null,"level":"none"},"debugger":{"detail":null,"level":"full"},"definition":{"detail":null,"level":"none"},"diagnostics":{"detail":null,"level":"none"},"documentHighlight":{"detail":null,"level":"none"},"documentSymbols":{"detail":null,"level":"none"},"formatting":{"detail":null,"level":"none"},"hover":{"detail":null,"level":"none"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":null,"level":"none"},"references":{"detail":null,"level":"none"},"rename":{"detail":null,"level":"none"},"repl":{"detail":"r","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":null,"level":"none"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"r","level":"full"},"typeDefinition":{"detail":null,"level":"none"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":null,"level":"none"}},"version":"4.5.3"},{"attrs":["ruby"],"componentVersions":{"iruby":"v0.8.2","ruby":"3.4.9","solargraph":"0.57.0"},"description":"Object-oriented language for quick and easy programming","displayName":"Ruby","extensions":["rb"],"iconMonochrome":"modules/kernels/ruby/ruby-monochrome.svg","id":"ruby","languageServers":{"available":["solargraph"],"enabledByDefault":["solargraph"],"probed":["solargraph"]},"support":{"codeActions":{"detail":null,"level":"none"},"completion":{"detail":"solargraph","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"solargraph","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"solargraph","level":"full"},"documentSymbols":{"detail":"solargraph","level":"full"},"formatting":{"detail":"rubocop","level":"full"},"hover":{"detail":"solargraph","level":"full"},"inlayHints":{"detail":null,"level":"none"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"solargraph","level":"full"},"references":{"detail":"solargraph","level":"full"},"rename":{"detail":"solargraph","level":"full"},"repl":{"detail":"irb","level":"full"},"semanticTokens":{"detail":null,"level":"none"},"signatureHelp":{"detail":"solargraph","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"ruby","level":"full"},"typeDefinition":{"detail":"solargraph","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"solargraph","level":"full"}},"version":"3.4.9"},{"attrs":["rust"],"componentVersions":{"cargo":"1.95.0","evcxr":"0.21.1","rust":"1.95.0","rust-analyzer":"2026-06-01"},"description":"Safe, concurrent, practical language (wrapper script)","displayName":"Rust","extensions":["rs","rlib"],"iconMonochrome":"modules/kernels/rust/rust-logo-monochrome.svg","id":"rust","languageServers":{"available":["rust-analyzer"],"enabledByDefault":["rust-analyzer"],"probed":["rust-analyzer"]},"support":{"codeActions":{"detail":"rust-analyzer","level":"full"},"completion":{"detail":"rust-analyzer","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"rust-analyzer","level":"full"},"diagnostics":{"detail":"pull + push","level":"full"},"documentHighlight":{"detail":"rust-analyzer","level":"full"},"documentSymbols":{"detail":"rust-analyzer","level":"full"},"formatting":{"detail":"rustfmt","level":"full"},"hover":{"detail":"rust-analyzer","level":"full"},"inlayHints":{"detail":"rust-analyzer","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"rust-analyzer","level":"full"},"references":{"detail":"rust-analyzer","level":"full"},"rename":{"detail":"rust-analyzer","level":"full"},"repl":{"detail":"evcxr","level":"full"},"semanticTokens":{"detail":"rust-analyzer","level":"full"},"signatureHelp":{"detail":"rust-analyzer","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"rust","level":"full"},"typeDefinition":{"detail":"rust-analyzer","level":"full"},"variableInspector":{"detail":null,"level":"full"},"workspaceSymbols":{"detail":"rust-analyzer","level":"full"}},"version":"1.95.0"},{"attrs":["typescript","ts"],"componentVersions":{"nodejs":"24.19.0","tslab":"1.0.22"},"description":"Event-driven I/O framework for the V8 JavaScript engine","displayName":"TypeScript","extensions":["ts","tsx"],"iconMonochrome":"modules/kernels/typescript/typescript-monochrome.svg","id":"typescript","languageServers":{"available":["typescript-language-server"],"enabledByDefault":["typescript-language-server"],"probed":["typescript-language-server"]},"support":{"codeActions":{"detail":"typescript-language-server","level":"full"},"completion":{"detail":"typescript-language-server","level":"full"},"debugger":{"detail":null,"level":"none"},"definition":{"detail":"typescript-language-server","level":"full"},"diagnostics":{"detail":"push","level":"full"},"documentHighlight":{"detail":"typescript-language-server","level":"full"},"documentSymbols":{"detail":"typescript-language-server","level":"full"},"formatting":{"detail":"tsserver","level":"full"},"hover":{"detail":"typescript-language-server","level":"full"},"inlayHints":{"detail":"typescript-language-server","level":"full"},"jupyterKernel":{"detail":null,"level":"full"},"languageServer":{"detail":"typescript-language-server","level":"full"},"references":{"detail":"typescript-language-server","level":"full"},"rename":{"detail":"typescript-language-server","level":"full"},"repl":{"detail":"node","level":"full"},"semanticTokens":{"detail":"typescript-language-server","level":"full"},"signatureHelp":{"detail":"typescript-language-server","level":"full"},"subPackageManagement":{"detail":null,"level":"full"},"syntaxHighlighting":{"detail":"javascript","level":"full"},"typeDefinition":{"detail":"typescript-language-server","level":"full"},"variableInspector":{"detail":null,"level":"none"},"workspaceSymbols":{"detail":"typescript-language-server","level":"full"}},"version":"24.19.0"}],"provenance":{"lsp":"Read from language server `initialize` responses (nix/lsp-capabilities.json, regenerated by scripts/probe-lsp-capabilities).","lspProbedKernels":["R","R-ark","bash","clojure","coq","cpp","go","haskell","javascript","julia","octave","postgres","pypy3","python3","ruby","rust","typescript"],"nix":"Evaluated from the codedown-languages module system (nix/feature-matrix.nix)."},"schemaVersion":1} \ No newline at end of file diff --git a/docs/feature-matrix.svg b/docs/feature-matrix.svg index b7b5be43..d52244e0 100644 --- a/docs/feature-matrix.svg +++ b/docs/feature-matrix.svg @@ -1,6 +1,6 @@ - + codedown language feature matrix - + Language feature support Generated from the codedown-languages Nix modules and live language server capabilities @@ -10,10 +10,10 @@ Code intelligence Navigation - - Editing - - Packages + + Editing + + Packages Jupyter kernel REPL Variable inspector @@ -32,11 +32,11 @@ Document outline Workspace symbol search Highlight occurrences - Formatting - Rename symbol - Code actions - Subpackage management - + Formatting + Rename symbol + Code actions + Subpackage management + Bash 5.3p9 Bash - Jupyter kernel: yes @@ -107,19 +107,19 @@ Bash - Formatting: yes (shfmt) - - shfmt + + Bash - Rename symbol: yes (bash-language-server) - - + + Bash - Code actions: yes (bash-language-server) - - + + Bash - Subpackage management: no - + C++ 23 21.1.8 @@ -194,21 +194,21 @@ C++ 23 - Formatting: yes (clang-format) - - clang-format + + C++ 23 - Rename symbol: yes (clangd) - - + + C++ 23 - Code actions: yes (clangd) - - + + C++ 23 - Subpackage management: no - + - + Clojure 1.12.5.1645 Clojure - Jupyter kernel: yes @@ -281,19 +281,19 @@ Clojure - Formatting: yes (cljfmt) - - cljfmt + + Clojure - Rename symbol: yes (clojure-lsp) - - + + Clojure - Code actions: yes (clojure-lsp) - - + + Clojure - Subpackage management: no - + Coq 9.1.1 @@ -355,19 +355,19 @@ Coq - Formatting: no - + Coq - Rename symbol: no - + Coq - Code actions: no - + Coq - Subpackage management: yes - - + + - + Go 1.26.6 Go - Jupyter kernel: yes @@ -440,19 +440,19 @@ Go - Formatting: yes (gofmt) - - gofmt + + Go - Rename symbol: yes (gopls) - - + + Go - Code actions: yes (gopls) - - + + Go - Subpackage management: no - + Haskell 9.10.3 @@ -527,22 +527,22 @@ Haskell - Formatting: yes (ormolu) - - ormolu + + Haskell - Rename symbol: yes (haskell-language-server) - - + + Haskell - Code actions: yes (haskell-language-server) - - + + Haskell - Subpackage management: yes - - + + - + JavaScript 24.19.0 JavaScript - Jupyter kernel: yes @@ -616,20 +616,20 @@ JavaScript - Formatting: yes (tsserver) - - tsserver + + JavaScript - Rename symbol: yes (typescript-language-server) - - + + JavaScript - Code actions: yes (typescript-language-server) - - + + JavaScript - Subpackage management: yes - - + + Julia 1.12.6 @@ -703,22 +703,22 @@ Julia - Formatting: yes (JuliaFormatter) - - JuliaFormatter + + Julia - Rename symbol: yes (LanguageServer) - - + + Julia - Code actions: yes (LanguageServer) - - + + Julia - Subpackage management: yes - - + + - + Octave 11.1.0 Octave - Jupyter kernel: yes @@ -780,17 +780,17 @@ Octave - Formatting: no - + Octave - Rename symbol: no - + Octave - Code actions: no - + Octave - Subpackage management: yes - - + + PostgreSQL 0.1 @@ -852,18 +852,18 @@ PostgreSQL - Formatting: no - + PostgreSQL - Rename symbol: no - + PostgreSQL - Code actions: no - + PostgreSQL - Subpackage management: no - + - + PyPy 7.3.20 PyPy - Jupyter kernel: yes @@ -937,19 +937,19 @@ PyPy - Formatting: no - + PyPy - Rename symbol: yes (jedi) - - + + PyPy - Code actions: yes (jedi) - - + + PyPy - Subpackage management: yes - - + + Python 3.13.15 @@ -1024,21 +1024,21 @@ Python - Formatting: no - + Python - Rename symbol: yes (jedi) - - + + Python - Code actions: yes (jedi) - - + + Python - Subpackage management: yes - - + + - + R 4.5.3 R - Jupyter kernel: yes @@ -1110,20 +1110,20 @@ R - Formatting: yes (styler) - - styler + + R - Rename symbol: yes (languageserver) - - + + R - Code actions: yes (languageserver) - - + + R - Subpackage management: yes - - + + R (Ark) 4.5.3 @@ -1187,19 +1187,19 @@ R (Ark) - Formatting: no - + R (Ark) - Rename symbol: no - + R (Ark) - Code actions: no - + R (Ark) - Subpackage management: yes - - + + - + Ruby 3.4.9 Ruby - Jupyter kernel: yes @@ -1272,19 +1272,19 @@ Ruby - Formatting: yes (rubocop) - - rubocop + + Ruby - Rename symbol: yes (solargraph) - - + + Ruby - Code actions: no - + Ruby - Subpackage management: yes - - + + Rust 1.95.0 @@ -1360,22 +1360,22 @@ Rust - Formatting: yes (rustfmt) - - rustfmt + + Rust - Rename symbol: yes (rust-analyzer) - - + + Rust - Code actions: yes (rust-analyzer) - - + + Rust - Subpackage management: yes - - + + - + TypeScript 24.19.0 TypeScript - Jupyter kernel: yes @@ -1449,25 +1449,25 @@ TypeScript - Formatting: yes (tsserver) - - tsserver + + TypeScript - Rename symbol: yes (typescript-language-server) - - + + TypeScript - Code actions: yes (typescript-language-server) - - + + TypeScript - Subpackage management: yes - - + + - + diff --git a/nix/feature-matrix.nix b/nix/feature-matrix.nix index f274b782..00441847 100644 --- a/nix/feature-matrix.nix +++ b/nix/feature-matrix.nix @@ -37,9 +37,6 @@ let # source = "nix": computed from the module evaluation below. # source = "lsp": read out of the language server's advertised capabilities. # source = "both": needs each (see `formatting` below). - # - # render = "label" asks the renderers to print the support detail in the cell rather than - # just a check mark. features = [ { id = "jupyterKernel"; name = "Jupyter kernel"; group = "core"; description = "Runs notebook cells through a Jupyter kernel."; source = "nix"; } @@ -81,8 +78,7 @@ let description = "Highlights other occurrences of the symbol under the cursor."; source = "lsp"; } { id = "formatting"; name = "Formatting"; group = "editing"; - description = "Format a document or a selection. The cell names the formatter behind it."; - source = "both"; render = "label"; } + description = "Format a document or a selection."; source = "both"; } { id = "rename"; name = "Rename symbol"; group = "editing"; description = "Rename a symbol and every reference to it."; source = "lsp"; } { id = "codeActions"; name = "Code actions"; group = "editing"; diff --git a/plans/inlay-hints-and-notebook-suffixes.md b/plans/inlay-hints-and-notebook-suffixes.md new file mode 100644 index 00000000..571e3d0c --- /dev/null +++ b/plans/inlay-hints-and-notebook-suffixes.md @@ -0,0 +1,160 @@ +# Inlay hints: what the channel needs to change + +Context from implementing LSP inlay hints in codedown (codedownio/codedown#1388, +part of codedownio/codedown#1366). The codedown side works: the request is +allowed, routed to the right server, positions are translated back to notebook +coordinates, and the hints are drawn in the editor. No language server the +channel ships can currently produce a hint in a notebook, for two separate +reasons. + +## How codedown asks + +A notebook is projected to each language server as a sub-document: the lines +belonging to other languages are blanked, and the file is named +``, e.g. `main.ipynb.py` for jedi. The server is +told the language through `language_id` in the `didOpen`. + +Requests are routed to a child server **by the line the request starts on**. So +codedown asks for inlay hints once per code cell, over that cell's own lines, +rather than once over the whole document. + +## Issue 1: rust-analyzer has type hints turned off + +rust-analyzer advertises `inlayHintProvider` and answers the request, but with +no hints. Its reported configuration is: + +```json +"inlayHints": { + "bindingModeHints": false, + "chainingHints": false, + "closingBraceHints": {"enable": true, "minLines": 25}, + "closureReturnTypeHints": false, + "lifetimeElisionHints": {"enable": "never", "useParameterNames": false}, + "parameterHints": false, + "reborrowHints": "never", + "renderColons": true, + "typeHints": {"enable": false, "hideClosureInitialization": false, "hideNamedConstructor": false} +} +``` + +`typeHints.enable` is false, so `let x = 5;` produces nothing. These are +rust-analyzer's own server-side defaults; the editor extensions that people +associate with rust-analyzer turn them on from the client side, which we don't. + +Fix: set them in the rust-analyzer language server's `initialization_options`, +the same mechanism `pylsp_initialization_options.nix` already uses. Something +like: + +```nix +initialization_options = { + inlayHints = { + typeHints.enable = true; + parameterHints = true; + chainingHints = true; + }; +}; +``` + +Worth deciding which categories are wanted by default rather than enabling +everything; parameter hints in particular are divisive. + +## Issue 2: rust-analyzer and clangd have no notebook_suffix + +Neither language server sets `notebook_suffix`, so a notebook's sub-document is +named exactly `main.ipynb`, with no extension telling the server what it is. + +- rust-analyzer tolerates this. It analyses the file from the `language_id` and + answers hovers, code actions and completions correctly. +- clangd does not. It refuses to build an AST for a file it doesn't recognise + and answers `-32602: trying to get AST for non-added document` to every + request that needs one, including inlay hints. clangd in a notebook is + effectively non-functional today. + +Fix: give both a `notebook_suffix`, as jedi (`.py`), python-lsp-server (`.py`), +bash-language-server (`.bash`) and tinymist (`.typ`) already do: + +- rust-analyzer: `notebook_suffix = ".rs"` +- clangd: `notebook_suffix = ".cpp"` + +Other servers advertising `inlayHintProvider` in `nix/lsp-capabilities.json` +are worth the same check: gopls, haskell-language-server, +typescript-language-server and julia's LanguageServer. + +## Related: formatters ship without their tools + +Same shape of problem, found while adding the format command +(codedownio/codedown#1381). Servers advertise a formatting capability and then +answer with no edits because the tool they format with isn't installed. + +- bash-language-server: fixed, `shfmt` added to its wrapper PATH. +- python-lsp-server: still affected. It is built from the bare package, so + neither `autopep8` nor `yapf` is importable and both `textDocument/formatting` + and `textDocument/rangeFormatting` return nothing. Adding `ps.autopep8` to the + environment in `modules/kernels/python/language_servers/language_server_pythonlsp/config.nix` + would make Python formatting work. + +`nix/lsp-capabilities.json` records what each server *advertises*, which is not +the same as what it can do. Six servers advertise range formatting; at least two +of them could not actually format until the tool was added. It may be worth a +note in that file, or a probe that asks for a formatting result rather than only +reading the capability. + +## No Python server here gives inlay hints, and basedpyright would + +Checked against the shipped packages rather than `nix/lsp-capabilities.json`, +which only records servers that are enabled by default (for python3 that is +jedi alone, so the file says nothing about the others): + +| server | implements inlay hints | +|---|---| +| jedi | no | +| pyright 1.1.411 | no | +| python-lsp-server 1.14.0 | no | +| basedpyright 1.39.3 | yes | + +pyright looks like a false positive if you grep for it: `inlayHintProvider` +appears twice in its tree, but both are in the bundled +`vscode-languageserver-protocol`, which every server using that library +contains. Its own `pyright-internal.js` and `pyright-langserver.js` have zero +occurrences. Open-source pyright never shipped hints; they were a Pylance +feature. python-lsp-server has no occurrence of "inlay" anywhere. + +basedpyright is a fork of pyright that re-adds the features Microsoft keeps in +the closed-source Pylance: inlay hints and semantic highlighting, with the +language server packaged so it works outside VS Code. It merges upstream +pyright, so the type-checking engine is the same code. Its +`basedpyright-langserver` sets `inlayHintProvider: true` and registers four +`onInlayHint` handlers. + +It is already in the nixpkgs this channel pins +(`e7215ec9581d62b85a3b1b870cd40cc353a06816`), at 1.39.3, as `pkgs.basedpyright`, +so adding it needs no pin change. Besides hints it would also give Python +semantic tokens, which is the other feature codedown wants +(P1.6 in codedown's plan). + +Two things to weigh: + +- It adds stricter rules of its own on top of pyright, plus a baseline + mechanism for adopting it on existing code. Those are configurable, but the + defaults should be looked at before shipping it to users. +- Our pyright is configured with `pyright.disableLanguageServices = true`, so + today it acts as a type checker beside jedi rather than as the editor server. + basedpyright would only provide hints if it is allowed to provide language + services, which is a decision about how the Python servers divide the work + rather than a packaging detail. + +## How to tell it worked + +In codedown, `codedown-tests/lsp/app/Spec/InlayHints.hs` currently asserts only +that the request goes out over the cell's lines, because nothing can answer it. +Once rust-analyzer's type hints are on, that test can assert the rendered hint +text instead: a cell containing `let x = 5;` should draw `: i32`. The same spec +has the assertion written and commented in its history. + +If basedpyright lands instead, the test is better off in Python: the standard +LSP test environment already has a Python kernel, so it would need no new nix +environment, and a cell containing `x = 5` should draw `: int`. The Rust +environment exists only because it was the cheapest server that advertised +hints; a C++ one was tried and rejected for adding 5.6 GiB to the test binary +cache, against 0.18 GiB for Rust, which shares a toolchain with an environment +already cached. diff --git a/scripts/render-feature-matrix.py b/scripts/render-feature-matrix.py index 7f5bc5ea..c740de4e 100755 --- a/scripts/render-feature-matrix.py +++ b/scripts/render-feature-matrix.py @@ -29,8 +29,6 @@ "rowStripe": "#f6f5f2", "yes": "#0ca30c", "yesFill": "#e4f4e4", - # Small text needs more contrast against yesFill than a check mark does. - "yesText": "#0a6e0a", "no": "#c3c2b7", }, "dark": { @@ -43,7 +41,6 @@ "rowStripe": "#201f1e", "yes": "#0ca30c", "yesFill": "#17301a", - "yesText": "#5fd45f", "no": "#4a4a47", }, } @@ -52,9 +49,6 @@ LABEL_WIDTH = 178 COL_WIDTH = 29 -# Rough advance width of the label columns' 9.5px text, used to size those columns. -LABEL_CHAR_WIDTH = 5.3 -LABEL_FONT_SIZE = 9.5 ROW_HEIGHT = 29 GROUP_BAND_HEIGHT = 22 PAD = 16 @@ -78,18 +72,6 @@ def detail_of(language, feature_id): return language["support"].get(feature_id, {}).get("detail") -def is_label_column(feature): - """Columns that print their detail (e.g. the formatter's name) instead of a check.""" - return feature.get("render") == "label" - - -def label_text(language, feature): - """What a label column shows for one language, or None for the not-supported dot.""" - if level_of(language, feature["id"]) != "full": - return None - return detail_of(language, feature["id"]) - - # --------------------------------------------------------------------------- # SVG # --------------------------------------------------------------------------- @@ -119,32 +101,14 @@ def diagonal_extent(name): header_height = int(max(diagonal_extent(f["name"]) for f in ordered)) + 14 - # Check-mark columns are all one width; a label column has to be wide enough for its - # longest value. - def column_width(feature): - if not is_label_column(feature): - return COL_WIDTH - widest = max((len(label_text(l, feature) or "") for l in languages), default=0) - return max(COL_WIDTH, int(widest * LABEL_CHAR_WIDTH) + 14) - - col_widths = [column_width(f) for f in ordered] - col_lefts = [] - running = 0 - for w in col_widths: - col_lefts.append(running) - running += w - grid_left = PAD + LABEL_WIDTH grid_top = PAD + TITLE_HEIGHT + GROUP_BAND_HEIGHT + header_height - grid_width = running + grid_width = COL_WIDTH * len(ordered) grid_height = ROW_HEIGHT * len(languages) - def center_of(i): - return grid_left + col_lefts[i] + col_widths[i] / 2 - # The rotated labels lean up and to the right, so the rightmost one overhangs the grid. # Whichever column's label reaches furthest right sets the canvas width. - rightmost = max(center_of(i) + 3 + diagonal_extent(f["name"]) + rightmost = max(grid_left + i * COL_WIDTH + COL_WIDTH / 2 + 3 + diagonal_extent(f["name"]) for i, f in enumerate(ordered)) width = int(max(rightmost, grid_left + grid_width)) + PAD height = grid_top + grid_height + LEGEND_HEIGHT + PAD @@ -171,8 +135,8 @@ def center_of(i): column = 0 for group in groups: count = sum(1 for f in ordered if f["group"] == group["id"]) - x = grid_left + col_lefts[column] - w = sum(col_widths[column:column + count]) + x = grid_left + column * COL_WIDTH + w = count * COL_WIDTH add(f' ') add(f' {escape(feature["name"])}') @@ -204,7 +168,7 @@ def center_of(i): f'text-anchor="end" fill="{theme["muted"]}">{escape(version)}') for i, feature in enumerate(ordered): - cx = center_of(i) + cx = grid_left + i * COL_WIDTH + COL_WIDTH / 2 cy = y + ROW_HEIGHT / 2 level = level_of(language, feature["id"]) detail = detail_of(language, feature["id"]) @@ -215,14 +179,7 @@ def center_of(i): tooltip += f" ({detail})" add(f' {escape(tooltip)}') - if is_label_column(feature) and label_text(language, feature): - text = label_text(language, feature) - add(f' ') - add(f' {escape(text)}') - elif level == "full": + if level == "full": add(f' ') add(f' ') add(f' Date: Thu, 8 Oct 2026 23:32:51 -0700 Subject: [PATCH 25/34] Regenerate the options docs after the description edits --- OPTIONS.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/OPTIONS.md b/OPTIONS.md index 5a66ffa7..39fe7f13 100644 --- a/OPTIONS.md +++ b/OPTIONS.md @@ -156,7 +156,7 @@ true **Typst formatter** -Which formatter tinymist uses\. Both are vendored into tinymist, so picking one costs nothing extra\. typstyle is also tinymist’s own default\. +Which formatter tinymist uses\. @@ -881,7 +881,7 @@ false **Go formatter** -Which formatter gopls uses\. Both are built into gopls; gofumpt is gofmt plus a set of stricter rules\. +Which formatter gopls uses\. @@ -1059,7 +1059,7 @@ false **Haskell formatter** -Which formatter haskell-language-server uses\. All of these are compiled into it, so picking one costs nothing extra\. +Which formatter haskell-language-server uses\. @@ -1809,7 +1809,7 @@ false **Python formatter** -Which formatter python-lsp-server uses\. It ships with none of them, so this also decides what gets added to the environment\. autopep8 is the lightest and reuses the pycodestyle settings above; black and ruff reformat more aggressively\. Note that Jedi, the default language server, can’t format at all – this only applies when python-lsp-server is enabled\. +Which formatter python-lsp-server uses\. @@ -2118,7 +2118,7 @@ false **Python formatter** -Which formatter python-lsp-server uses\. It ships with none of them, so this also decides what gets added to the environment\. autopep8 is the lightest and reuses the pycodestyle settings above; black and ruff reformat more aggressively\. Note that Jedi, the default language server, can’t format at all – this only applies when python-lsp-server is enabled\. +Which formatter python-lsp-server uses\. @@ -2279,8 +2279,6 @@ true **Format Ruby code with rubocop** -Solargraph formats by running rubocop’s autocorrect over the cell, using the configuration below\. Turning this off also stops Solargraph from advertising formatting to the editor\. - *Type:* From 6c9321580cb6c3a02122d2b4088b38673eeb4ee9 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 9 Oct 2026 02:24:48 -0700 Subject: [PATCH 26/34] Tests: move the formatting helpers into TestLib.LSP.Formatting --- tests/src/TestLib/LSP.hs | 181 ++-------------------------- tests/src/TestLib/LSP/Formatting.hs | 85 +++++++++++++ tests/src/TestLib/LSP/Session.hs | 91 ++++++++++++++ tests/tests.cabal | 2 + 4 files changed, 189 insertions(+), 170 deletions(-) create mode 100644 tests/src/TestLib/LSP/Formatting.hs create mode 100644 tests/src/TestLib/LSP/Session.hs diff --git a/tests/src/TestLib/LSP.hs b/tests/src/TestLib/LSP.hs index b10f5f1b..7888654f 100644 --- a/tests/src/TestLib/LSP.hs +++ b/tests/src/TestLib/LSP.hs @@ -5,10 +5,7 @@ {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} module TestLib.LSP ( - findLspConfig - , getPathAndNixEnvironmentClosure - - , doNotebookSession + doNotebookSession , doSession' , doSession'' @@ -24,10 +21,7 @@ module TestLib.LSP ( , itHasHoverSatisfying - , itFormatsAs - , itFormatsAs' - , itReformats - , formatsAs + , module TestLib.LSP.Formatting , Helpers.getHoverOrException , Helpers.allHoverText @@ -36,9 +30,7 @@ module TestLib.LSP ( , LspContext ) where -import Control.Monad import Control.Monad.IO.Unlift -import Control.Monad.Logger (MonadLogger) import Control.Monad.Reader import Data.Aeson as A import qualified Data.ByteString as B @@ -50,16 +42,12 @@ import GHC.Int import GHC.Stack import Language.LSP.Protocol.Types import Language.LSP.Test -import Language.LSP.Test.Helpers (LanguageServerConfig(..), LspContext, LspSessionOptions(..), defaultLspSessionOptions, withLspSession) +import Language.LSP.Test.Helpers (LspContext, LspSessionOptions(..), withLspSession) import qualified Language.LSP.Test.Helpers as Helpers -import System.FilePath import Test.Sandwich as Sandwich -import Test.Sandwich.Waits (waitUntil) +import TestLib.LSP.Formatting +import TestLib.LSP.Session import TestLib.Types -import UnliftIO.Directory -import UnliftIO.Exception -import UnliftIO.IO -import UnliftIO.Process doNotebookSession :: ( @@ -76,17 +64,8 @@ doSession'' :: ( LspContext ctx m, HasNixEnvironment ctx ) => Text -> Text -> Text -> [(FilePath, B.ByteString)] -> (Helpers.LspSessionInfo -> Session (ExampleT ctx m) a) -> ExampleT ctx m a doSession'' filename lsName codeToUse extraFiles cb = do - lspConfig <- findLspConfig lsName - (pathToUse, closure) <- getPathAndNixEnvironmentClosure - let lspSessionOptions = (defaultLspSessionOptions lspConfig) { - lspSessionOptionsInitialFileName = T.unpack filename - , lspSessionOptionsInitialLanguageKind = LanguageKind_Python - , lspSessionOptionsInitialCode = codeToUse - , lspSessionOptionsExtraFiles = extraFiles - , lspSessionOptionsReadOnlyBinds = closure - , lspSessionOptionsPathEnvVar = pathToUse - } - withLspSession lspSessionOptions cb + lspSessionOptions <- lspSessionOptionsFor lsName (T.unpack filename) LanguageKind_Python codeToUse + withLspSession (lspSessionOptions { lspSessionOptionsExtraFiles = extraFiles }) cb testDiagnostics :: ( LspContext ctx m, HasNixEnvironment ctx @@ -107,16 +86,7 @@ testDiagnosticsLabelDesired :: ( LspContext ctx m, HasNixEnvironment ctx ) => String -> Text -> FilePath -> LanguageKind -> Text -> ([Diagnostic] -> Bool) -> SpecFree ctx m () testDiagnosticsLabelDesired label name filename languageKind code cb = it label $ do - lspConfig <- findLspConfig name - (pathToUse, closure) <- getPathAndNixEnvironmentClosure - - let lspSessionOptions = (defaultLspSessionOptions lspConfig) { - lspSessionOptionsInitialFileName = filename - , lspSessionOptionsInitialLanguageKind = languageKind - , lspSessionOptionsInitialCode = code - , lspSessionOptionsReadOnlyBinds = closure - , lspSessionOptionsPathEnvVar = pathToUse - } + lspSessionOptions <- lspSessionOptionsFor name filename languageKind code Helpers.testDiagnostics lspSessionOptions languageKind $ \diags -> if | cb diags -> return () @@ -126,17 +96,8 @@ testDiagnostics'' :: ( LspContext ctx m, HasNixEnvironment ctx ) => String -> Text -> FilePath -> LanguageKind -> Text -> [(FilePath, B.ByteString)] -> ([Diagnostic] -> ExampleT ctx m ()) -> SpecFree ctx m () testDiagnostics'' label name filename languageKind code extraFiles cb = it label $ do - lspConfig <- findLspConfig name - (pathToUse, closure) <- getPathAndNixEnvironmentClosure - - let lspSessionOptions = (defaultLspSessionOptions lspConfig) { - lspSessionOptionsInitialFileName = filename - , lspSessionOptionsInitialLanguageKind = languageKind - , lspSessionOptionsInitialCode = code - , lspSessionOptionsReadOnlyBinds = closure - , lspSessionOptionsPathEnvVar = pathToUse - , lspSessionOptionsExtraFiles = extraFiles - } + lspSessionOptions <- (\o -> o { lspSessionOptionsExtraFiles = extraFiles }) + <$> lspSessionOptionsFor name filename languageKind code Helpers.testDiagnostics lspSessionOptions languageKind $ \diags -> do lift $ cb diags @@ -145,16 +106,7 @@ itHasHoverSatisfying :: ( LspContext ctx m, HasNixEnvironment ctx ) => Text -> FilePath -> LanguageKind -> Text -> Position -> (Hover -> ExampleT ctx m ()) -> SpecFree ctx m () itHasHoverSatisfying name filename languageKind code pos cb = it [i|#{name}: #{show code} (hover)|] $ do - lspConfig <- findLspConfig name - (pathToUse, closure) <- getPathAndNixEnvironmentClosure - - let lspSessionOptions = (defaultLspSessionOptions lspConfig) { - lspSessionOptionsInitialFileName = filename - , lspSessionOptionsInitialLanguageKind = languageKind - , lspSessionOptionsInitialCode = code - , lspSessionOptionsReadOnlyBinds = closure - , lspSessionOptionsPathEnvVar = pathToUse - } + lspSessionOptions <- lspSessionOptionsFor name filename languageKind code withLspSession lspSessionOptions $ \_ -> do ident <- openDoc filename languageKind @@ -162,117 +114,6 @@ itHasHoverSatisfying name filename languageKind code pos cb = it [i|#{name}: #{s Nothing -> expectationFailure [i|Expected a hover.|] Just x -> lift $ cb x --- | Format a document and check the result, which is the only way to tell a working formatter --- from a server that advertises documentFormattingProvider and then can't find its formatter. -itFormatsAs :: ( - LspContext ctx m, HasNixEnvironment ctx - ) => Text -> FilePath -> LanguageKind -> Text -> Text -> SpecFree ctx m () -itFormatsAs = itFormatsAs' 180 - --- | 'itFormatsAs' with an explicit timeout, for servers that need to load a project first. -itFormatsAs' :: ( - LspContext ctx m, HasNixEnvironment ctx - ) => Double -> Text -> FilePath -> LanguageKind -> Text -> Text -> SpecFree ctx m () -itFormatsAs' timeoutSeconds name filename languageKind code expected = - itFormats' timeoutSeconds name filename languageKind code (`shouldBe` expected) - --- | Weaker than 'itFormatsAs': only that formatting rewrote the document. For formatters whose --- exact output we haven't pinned down, this still catches a server that advertises formatting --- and can't deliver it. -itReformats :: ( - LspContext ctx m, HasNixEnvironment ctx - ) => Text -> FilePath -> LanguageKind -> Text -> SpecFree ctx m () -itReformats name filename languageKind code = - itFormats' 180 name filename languageKind code (`shouldNotBe` code) - -itFormats' :: ( - LspContext ctx m, HasNixEnvironment ctx - ) => Double -> Text -> FilePath -> LanguageKind -> Text -> (Text -> ExampleT ctx m ()) -> SpecFree ctx m () --- Deliberately keeps the code out of the test name: Sandwich names the test's directory after --- it, and the escaped newlines turn into backslashes in a path, which R.cache rewrites to --- forward slashes and then can't write to. -itFormats' timeoutSeconds name filename languageKind code cb = - it [i|#{name} formats #{filename}|] $ - formats' timeoutSeconds name filename languageKind code cb - --- | The body of 'itFormatsAs', for specs that need to wrap it (e.g. to mark it 'pending'). -formatsAs :: ( - LspContext ctx m, HasNixEnvironment ctx - ) => Double -> Text -> FilePath -> LanguageKind -> Text -> Text -> ExampleT ctx m () -formatsAs timeoutSeconds name filename languageKind code expected = - formats' timeoutSeconds name filename languageKind code (`shouldBe` expected) - -formats' :: ( - LspContext ctx m, HasNixEnvironment ctx - ) => Double -> Text -> FilePath -> LanguageKind -> Text -> (Text -> ExampleT ctx m ()) -> ExampleT ctx m () -formats' timeoutSeconds name filename languageKind code cb = do - lspConfig <- findLspConfig name - (pathToUse, closure) <- getPathAndNixEnvironmentClosure - - let lspSessionOptions = (defaultLspSessionOptions lspConfig) { - lspSessionOptionsInitialFileName = filename - , lspSessionOptionsInitialLanguageKind = languageKind - , lspSessionOptionsInitialCode = code - , lspSessionOptionsReadOnlyBinds = closure - , lspSessionOptionsPathEnvVar = pathToUse - } - - withLspSession lspSessionOptions $ \_ -> do - ident <- openDoc filename languageKind - -- formatDoc applies the edits to the session's copy of the document, so retrying after - -- the server has warmed up is harmless: a formatted document just comes back unchanged. - waitUntil timeoutSeconds $ do - formatDoc ident formattingOptions - documentContents ident >>= lift . cb - -formattingOptions :: FormattingOptions -formattingOptions = FormattingOptions 2 True Nothing Nothing Nothing - -findLspConfig :: ( - MonadIO m, MonadLogger m, MonadReader context m, Sandwich.HasLabel context "nixEnvironment" FilePath - ) => Text -> m LanguageServerConfig -findLspConfig name = do - languageServersPath <- ( "lib" "codedown" "language-servers") <$> getContext nixEnvironment - languageServerFiles <- filter (\x -> ".yaml" `T.isSuffixOf` T.pack x) <$> listDirectory languageServersPath - lspConfigs :: [LanguageServerConfig] <- (mconcat <$>) $ forM languageServerFiles $ \((languageServersPath ) -> path) -> do - liftIO (A.eitherDecodeFileStrict path) >>= \case - Left err -> expectationFailure [i|Failed to decode language server path '#{path}': #{err}|] - Right x -> return x - - config <- case L.find (\x -> lspConfigName x == name) lspConfigs of - Nothing -> expectationFailure [i|Couldn't find LSP config: #{name}. Had: #{fmap lspConfigName lspConfigs}|] - Just x -> do - info [i|LSP config: #{A.encode x}|] - return x - - return config - -getBasicPath :: ( - MonadUnliftIO m, MonadLogger m, MonadReader context m, Sandwich.HasLabel context "nixEnvironment" FilePath - ) => m FilePath -getBasicPath = do - bracket (openFile "/dev/null" WriteMode) hClose $ \devNullHandle -> - (T.unpack . T.strip . T.pack) <$> readCreateProcess ((proc "nix" ["run", ".#print-basic-path"]) { std_err = UseHandle devNullHandle }) "" - -getPathAndNixEnvironmentClosure :: ( - MonadUnliftIO m, MonadLogger m - , MonadReader context m, HasBaseContext context, Sandwich.HasLabel context "nixEnvironment" FilePath - ) => m (FilePath, [FilePath]) -getPathAndNixEnvironmentClosure = do - pathToUse <- getBasicPath - - -- Get the full closure of the Nix environment and jupyter runner - nixEnv <- getContext nixEnvironment - closure <- (fmap T.unpack . Prelude.filter (/= "") . T.splitOn "\n" . T.pack) <$> readCreateProcessWithLogging ( - proc "nix" (["path-info", "-r" - , nixEnv - ] - <> (splitSearchPath pathToUse) - ) - ) "" - - return (pathToUse, closure) - assertDiagnosticRanges :: (HasCallStack, MonadIO m) => [Diagnostic] -> [(Range, Maybe (Int32 |? Text))] -> ExampleT ctx m () assertDiagnosticRanges = assertDiagnosticRanges'' Helpers.getDiagnosticRanges diff --git a/tests/src/TestLib/LSP/Formatting.hs b/tests/src/TestLib/LSP/Formatting.hs new file mode 100644 index 00000000..3412ff11 --- /dev/null +++ b/tests/src/TestLib/LSP/Formatting.hs @@ -0,0 +1,85 @@ +-- | Checking that a language server really formats. +-- +-- Advertising @documentFormattingProvider@ proves nothing: a server will happily offer it and +-- then fail every request because the tool behind it isn't installed, which is what +-- bash-language-server did until shfmt was added to its wrapper. So these send a real +-- @textDocument/formatting@ and look at what comes back. + +module TestLib.LSP.Formatting ( + itFormatsAs + , itFormatsAs' + , itReformats + + , formatsAs + ) where + +import Control.Monad.Reader +import Data.String.Interpolate +import Data.Text (Text) +import Language.LSP.Protocol.Types +import Language.LSP.Test +import Language.LSP.Test.Helpers (LspContext, withLspSession) +import Test.Sandwich as Sandwich +import Test.Sandwich.Waits (waitUntil) +import TestLib.LSP.Session +import TestLib.Types + + +-- | Format a document and check the result against the text it should become. +itFormatsAs :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Text -> FilePath -> LanguageKind -> Text -> Text -> SpecFree ctx m () +itFormatsAs = itFormatsAs' defaultTimeout + +-- | 'itFormatsAs' with an explicit timeout, for servers that need to load a project first. +itFormatsAs' :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Double -> Text -> FilePath -> LanguageKind -> Text -> Text -> SpecFree ctx m () +itFormatsAs' timeoutSeconds name filename languageKind code expected = + itFormats' timeoutSeconds name filename languageKind code (`shouldBe` expected) + +-- | Weaker than 'itFormatsAs': only that formatting rewrote the document. For formatters whose +-- exact output we haven't pinned down, this still catches a server that advertises formatting +-- and can't deliver it. +itReformats :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Text -> FilePath -> LanguageKind -> Text -> SpecFree ctx m () +itReformats name filename languageKind code = + itFormats' defaultTimeout name filename languageKind code (`shouldNotBe` code) + +-- | The body of 'itFormatsAs', for specs that need to wrap it (e.g. to mark it 'pending'). +formatsAs :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Double -> Text -> FilePath -> LanguageKind -> Text -> Text -> ExampleT ctx m () +formatsAs timeoutSeconds name filename languageKind code expected = + formats' timeoutSeconds name filename languageKind code (`shouldBe` expected) + +itFormats' :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Double -> Text -> FilePath -> LanguageKind -> Text -> (Text -> ExampleT ctx m ()) -> SpecFree ctx m () +-- Deliberately keeps the code out of the test name: Sandwich names the test's directory after +-- it, and the escaped newlines turn into backslashes in a path, which R.cache rewrites to +-- forward slashes and then can't write to. +itFormats' timeoutSeconds name filename languageKind code cb = + it [i|#{name} formats #{filename}|] $ + formats' timeoutSeconds name filename languageKind code cb + +formats' :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Double -> Text -> FilePath -> LanguageKind -> Text -> (Text -> ExampleT ctx m ()) -> ExampleT ctx m () +formats' timeoutSeconds name filename languageKind code cb = do + lspSessionOptions <- lspSessionOptionsFor name filename languageKind code + + withLspSession lspSessionOptions $ \_ -> do + ident <- openDoc filename languageKind + -- formatDoc applies the edits to the session's copy of the document, so retrying after + -- the server has warmed up is harmless: a formatted document just comes back unchanged. + waitUntil timeoutSeconds $ do + formatDoc ident formattingOptions + documentContents ident >>= lift . cb + +defaultTimeout :: Double +defaultTimeout = 180 + +formattingOptions :: FormattingOptions +formattingOptions = FormattingOptions 2 True Nothing Nothing Nothing diff --git a/tests/src/TestLib/LSP/Session.hs b/tests/src/TestLib/LSP/Session.hs new file mode 100644 index 00000000..b7deb75a --- /dev/null +++ b/tests/src/TestLib/LSP/Session.hs @@ -0,0 +1,91 @@ +-- | Starting a language server out of a built Nix environment. +-- +-- Split out of "TestLib.LSP" so that "TestLib.LSP.Formatting" can use it without the two +-- importing each other. + +module TestLib.LSP.Session ( + findLspConfig + , getPathAndNixEnvironmentClosure + , lspSessionOptionsFor + ) where + +import Control.Monad +import Control.Monad.IO.Unlift +import Control.Monad.Logger (MonadLogger) +import Control.Monad.Reader +import Data.Aeson as A +import qualified Data.List as L +import Data.String.Interpolate +import qualified Data.Text as T +import Data.Text (Text) +import Language.LSP.Protocol.Types +import Language.LSP.Test.Helpers (LanguageServerConfig(..), LspContext, LspSessionOptions(..), defaultLspSessionOptions) +import System.FilePath +import Test.Sandwich as Sandwich +import TestLib.Types +import UnliftIO.Directory +import UnliftIO.Exception +import UnliftIO.IO +import UnliftIO.Process + + +-- | Options for a session over a single file, which is what nearly every LSP test wants. +lspSessionOptionsFor :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => Text -> FilePath -> LanguageKind -> Text -> ExampleT ctx m LspSessionOptions +lspSessionOptionsFor name filename languageKind code = do + lspConfig <- findLspConfig name + (pathToUse, closure) <- getPathAndNixEnvironmentClosure + + return $ (defaultLspSessionOptions lspConfig) { + lspSessionOptionsInitialFileName = filename + , lspSessionOptionsInitialLanguageKind = languageKind + , lspSessionOptionsInitialCode = code + , lspSessionOptionsReadOnlyBinds = closure + , lspSessionOptionsPathEnvVar = pathToUse + } + +findLspConfig :: ( + MonadIO m, MonadLogger m, MonadReader context m, Sandwich.HasLabel context "nixEnvironment" FilePath + ) => Text -> m LanguageServerConfig +findLspConfig name = do + languageServersPath <- ( "lib" "codedown" "language-servers") <$> getContext nixEnvironment + languageServerFiles <- filter (\x -> ".yaml" `T.isSuffixOf` T.pack x) <$> listDirectory languageServersPath + lspConfigs :: [LanguageServerConfig] <- (mconcat <$>) $ forM languageServerFiles $ \((languageServersPath ) -> path) -> do + liftIO (A.eitherDecodeFileStrict path) >>= \case + Left err -> expectationFailure [i|Failed to decode language server path '#{path}': #{err}|] + Right x -> return x + + config <- case L.find (\x -> lspConfigName x == name) lspConfigs of + Nothing -> expectationFailure [i|Couldn't find LSP config: #{name}. Had: #{fmap lspConfigName lspConfigs}|] + Just x -> do + info [i|LSP config: #{A.encode x}|] + return x + + return config + +getBasicPath :: ( + MonadUnliftIO m, MonadLogger m, MonadReader context m, Sandwich.HasLabel context "nixEnvironment" FilePath + ) => m FilePath +getBasicPath = do + bracket (openFile "/dev/null" WriteMode) hClose $ \devNullHandle -> + (T.unpack . T.strip . T.pack) <$> readCreateProcess ((proc "nix" ["run", ".#print-basic-path"]) { std_err = UseHandle devNullHandle }) "" + +getPathAndNixEnvironmentClosure :: ( + MonadUnliftIO m, MonadLogger m + , MonadReader context m, HasBaseContext context, Sandwich.HasLabel context "nixEnvironment" FilePath + ) => m (FilePath, [FilePath]) +getPathAndNixEnvironmentClosure = do + pathToUse <- getBasicPath + + -- Get the full closure of the Nix environment and jupyter runner + nixEnv <- getContext nixEnvironment + closure <- (fmap T.unpack . Prelude.filter (/= "") . T.splitOn "\n" . T.pack) <$> readCreateProcessWithLogging ( + proc "nix" (["path-info", "-r" + , nixEnv + ] + <> (splitSearchPath pathToUse) + ) + ) "" + + return (pathToUse, closure) diff --git a/tests/tests.cabal b/tests/tests.cabal index c04cc909..564f69fa 100644 --- a/tests/tests.cabal +++ b/tests/tests.cabal @@ -24,6 +24,8 @@ library TestLib.JupyterRunnerContext TestLib.JupyterTypes TestLib.LSP + TestLib.LSP.Formatting + TestLib.LSP.Session TestLib.NixEnvironmentContext TestLib.NixRendering TestLib.NixTypes From df161e2b16a1a4953e455c064741aa5d1ade2227 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 9 Oct 2026 02:24:48 -0700 Subject: [PATCH 27/34] Write up what it would take to format notebook cells through the proxies --- plans/notebook-formatting-in-proxies.md | 125 ++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 plans/notebook-formatting-in-proxies.md diff --git a/plans/notebook-formatting-in-proxies.md b/plans/notebook-formatting-in-proxies.md new file mode 100644 index 00000000..d72078db --- /dev/null +++ b/plans/notebook-formatting-in-proxies.md @@ -0,0 +1,125 @@ +# Plan: make textDocument/formatting work for notebook cells + +## Where this came from + +Four languages reach their language server through a notebook proxy: Rust +(rust-notebook-language-server), Haskell (haskell-notebook-language-server), C++ +(cpp-notebook-language-server) and Go (go-notebook-language-server). Each one projects the +notebook's cells into a document the server will accept, registers it under a different URI, +and rewrites requests and responses between the two coordinate spaces. + +None of them handled `textDocument/formatting`, so the request went to the server naming a +document it had never opened and failed. rust-notebook-language-server#2 fixed it for Rust. +The other three still can't format a cell, and the reason isn't the missing handler — it's that +their projections can't be undone once a formatter has rewritten the document. + +## Why formatting is different from every other request + +Hover, completion, go-to-definition and inlay hints all point *at a place*. Mapping them back is +a position-at-a-time job, which the transformers already do via `untransformPosition`. + +Formatting rewrites the whole document. The edits only mean anything applied together, so the +response can't be mapped edit-by-edit — you have to apply them, get the formatted document, and +carry *that* back to the original's coordinates. rust-notebook-language-server#2 added +`unproject :: Params a -> a -> Doc -> Maybe Doc` to the `Transformer` class for exactly this: the +dual of `project`, returning `Nothing` when a transformer threw away something it would need. + +It works for Rust because the whole projection is "wrap the cell in `fn main() { … }`": drop two +lines, undo one level of indentation, done. + +It doesn't work for the others because their projections move lines around: + +| Proxy | Projection | +| --- | --- | +| cpp, go | `DeclarationSifter` — a line *permutation* (it stores `forward`/`inverse` index vectors), plus a package header, plus a wrapper function, plus a 2-column indent on the wrapper body | +| haskell | Seven transformers: directives to pragmas, directives stripped, expressions and statements rewritten to declarations, imports sifted to the top, an injected import, pragmas sifted above the imports | + +All of those depend on knowing which output line came from which input line. A formatter destroys +that: it splits long lines, joins short ones, inserts and deletes blanks. + +## The idea: track lines through the format with marker comments + +Tag each line the projection moves with a comment carrying its original index, so the mapping can +be read back out of the formatted document: + +```go +import "fmt" //@3 +func _notebookExec() { + x := 1 //@0 + fmt.Println(x) //@1 +} +``` + +### What was measured + +| Formatter | Marker survives? | Notes | +| --- | --- | --- | +| gofmt | yes | stays attached to its line, gets aligned; stripping it leaves alignment padding to trim. No column limit, so no distortion | +| clang-format | yes | stays attached; when a line is split the marker lands on the **last** fragment | +| ormolu | yes, but | `--@0` is a **parse error** — Haskell lexes `--@` as an operator, not a comment. It has to be `-- @0`. ormolu also inserts blank lines of its own, which is fine | + +One real catch, demonstrated with clang-format: the marker consumes column budget. A 78-character +line that clang-format leaves alone gets split once a 6-character marker is appended. So markers +change the formatted result, not just the bookkeeping. + +Mitigation: fixed-width markers plus a `ColumnLimit` raised by exactly that width. The proxy owns +the shadow directory, so it can write a `.clang-format` there. ormolu has no column setting, and +gofmt has no limit at all. + +### What an implementation needs + +1. **Attribution.** Every output line maps to an original index or to "synthetic". A marked line + maps to its index; a run of unmarked lines attaches to the following marker (matching where + clang-format leaves it after a split). Header and wrapper lines are identified positionally or + carry their own markers. +2. **Reassembly.** Group output lines by original index, sort, concatenate, strip the markers and + the whitespace they attracted. +3. **Knowing when to stop.** If an index has vanished because the formatter deleted its line, or + the indices come back in an order that can't be reconciled, return `Nothing`. The `Maybe` in + `unproject` is where that lives. + +## The better shape: format against a scratch document + +Markers in the real shadow document would also be seen by diagnostics, completion and hover. You +only want them while formatting, which points somewhere else: + +On a formatting request, the proxy opens a *second* document (`.fmt.`) holding the +sub-document projected **for formatting only**, asks the server to format that, reads the result, +and closes it. + +That buys three things: + +- Markers never touch the document everything else uses. +- The formatting projection can be far simpler than the analysis one. cpp and go only need "make + it parse" — a package header and a function wrapper, no sifting, no permutation. At that point + `unproject` is the easy Rust case and **no markers are needed at all**. +- The proxy owns the scratch file's directory, so clang-format's column budget is fixable there. + +Costs: two extra round trips per format, and the server will publish diagnostics against the +scratch URI which the proxy has to swallow. + +## Effort + +- **cpp, go** — port `unproject` to their `Transformer` class, add the scratch-document path with + a minimal wrapper. No markers. Roughly a day each with tests. This is the one worth doing. +- **haskell** — the hard one. Even a minimal projection still needs `ExpressionToDeclaration` and + `StatementToDeclaration` to make a cell parse, and those rewrite lines in place. They're + invertible per line if the rewrite is recorded, but the attribution still has to survive ormolu, + so Haskell genuinely needs the marker scheme. Several days, and the `-- @N` spacing quirk is a + reminder that each language wants its own marker syntax. + +## Check this first + +Everything above assumes a formatting request covers a whole per-language sub-document. codedown +routes requests to a child server by the line the request starts on, and asks for inlay hints once +per cell. If it also formats per cell, the scratch document should hold just that cell, and all of +this gets easier. Worth confirming on the codedown side before building anything. + +## Current state + +- Rust: works. rust-notebook-language-server#2, merged. +- C++, Go, Haskell: formatting a cell fails with a server error. Inlay hints are fixed separately + (cpp-notebook-language-server#1, go-notebook-language-server#1, + haskell-notebook-language-server#2). Plain `.rs` / `.go` / `.cpp` / `.hs` files were never + affected — the proxies leave a non-notebook URI alone, so formatting already worked there, and + the tests in `tests/app/Spec/Tests/` cover that path. From 565a49383f28859645b4876ea66b38f4c1670190 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 9 Oct 2026 03:31:33 -0700 Subject: [PATCH 28/34] R: record what actually causes the formatting test to hang --- FEATURE-MATRIX.md | 12 +++-- plans/inlay-hints-and-notebook-suffixes.md | 60 +++++++++++----------- tests/app/Spec/Tests/R.hs | 13 ++--- 3 files changed, 45 insertions(+), 40 deletions(-) diff --git a/FEATURE-MATRIX.md b/FEATURE-MATRIX.md index 71f389b9..c90f017f 100644 --- a/FEATURE-MATRIX.md +++ b/FEATURE-MATRIX.md @@ -221,9 +221,15 @@ Caveats the table above can't show: [rust-notebook-language-server#2](https://github.com/codedownio/rust-notebook-language-server/pull/2) fixes it; the Rust formatting test stays pending until that's released and the pinned version here moves up. -- The R formatting test is pending too. styler produces the right answer — you can watch it - come back over the wire in the session log — but the R language server then never answers - lsp-test's `shutdown` and the session hangs rather than finishing. +- The R formatting test is pending too, and it isn't R's fault. styler produces the right + answer and the server delivers it: with `languageserver::run(debug = …)` turned on, its log + shows the shutdown response going out and a clean exit 0. lsp-test never reads that + response. A `publishDiagnostics` notification arrives in the window between the shutdown + request and its reply — 11 ms before it — and the session deadlocks in teardown. The 120s + timeout around the shutdown exchange never fires; the process sits idle for half an hour on + 1.5 seconds of CPU. Replaying the identical byte sequence against the same server in the + same sandbox always gets the response, including pipelined sends and with stdin closed, so + the fix belongs in lsp-test's teardown path. ## Known gaps diff --git a/plans/inlay-hints-and-notebook-suffixes.md b/plans/inlay-hints-and-notebook-suffixes.md index 571e3d0c..ca78f9cc 100644 --- a/plans/inlay-hints-and-notebook-suffixes.md +++ b/plans/inlay-hints-and-notebook-suffixes.md @@ -4,8 +4,8 @@ Context from implementing LSP inlay hints in codedown (codedownio/codedown#1388, part of codedownio/codedown#1366). The codedown side works: the request is allowed, routed to the right server, positions are translated back to notebook coordinates, and the hints are drawn in the editor. No language server the -channel ships can currently produce a hint in a notebook, for two separate -reasons. +channel ships can currently produce a hint in a notebook. For rust-analyzer the +reason is configuration; for clangd it is packaging. ## How codedown asks @@ -37,47 +37,45 @@ no hints. Its reported configuration is: } ``` -`typeHints.enable` is false, so `let x = 5;` produces nothing. These are -rust-analyzer's own server-side defaults; the editor extensions that people -associate with rust-analyzer turn them on from the client side, which we don't. +`typeHints.enable` is false, so `let x = 5;` produces nothing. This is not +rust-analyzer defaulting them off: the whole `inlayHints` block is written out +explicitly in +`modules/kernels/rust/language_server_rust_analyzer/config.nix`, around line +134, with every category disabled. What the server reports back is our own +configuration. -Fix: set them in the rust-analyzer language server's `initialization_options`, -the same mechanism `pylsp_initialization_options.nix` already uses. Something -like: +Fix: flip the categories we want in that existing block. The smallest change +that makes hints appear at all is ```nix -initialization_options = { - inlayHints = { - typeHints.enable = true; - parameterHints = true; - chainingHints = true; - }; +"typeHints" = { + "enable" = true; + ... }; ``` -Worth deciding which categories are wanted by default rather than enabling -everything; parameter hints in particular are divisive. +Worth deciding which of the others are wanted rather than enabling everything; +parameter hints in particular are divisive. -## Issue 2: rust-analyzer and clangd have no notebook_suffix +## Issue 2: clangd has no notebook_suffix -Neither language server sets `notebook_suffix`, so a notebook's sub-document is -named exactly `main.ipynb`, with no extension telling the server what it is. +A notebook's sub-document is named ``. clangd sets no +suffix, so it is handed a file called `main.ipynb`, refuses to build an AST for +something it doesn't recognise, and answers `-32602: trying to get AST for +non-added document` to every request that needs one. clangd in a notebook is +effectively non-functional today, inlay hints included. -- rust-analyzer tolerates this. It analyses the file from the `language_id` and - answers hovers, code actions and completions correctly. -- clangd does not. It refuses to build an AST for a file it doesn't recognise - and answers `-32602: trying to get AST for non-added document` to every - request that needs one, including inlay hints. clangd in a notebook is - effectively non-functional today. +Fix: `notebook_suffix = ".cpp"`, as jedi (`.py`), python-lsp-server (`.py`), +bash-language-server (`.bash`) and tinymist (`.typ`) already do. -Fix: give both a `notebook_suffix`, as jedi (`.py`), python-lsp-server (`.py`), -bash-language-server (`.bash`) and tinymist (`.typ`) already do: - -- rust-analyzer: `notebook_suffix = ".rs"` -- clangd: `notebook_suffix = ".cpp"` +rust-analyzer also has an empty suffix, but that one is deliberate and should be +left alone: `notebook_suffix = if raw then ".rs" else ""`, because the non-raw +configuration wraps it in `rust-notebook-language-server`, which does its own +projection. rust-analyzer answers hovers, code actions and completions in a +notebook today, so nothing here is broken. Other servers advertising `inlayHintProvider` in `nix/lsp-capabilities.json` -are worth the same check: gopls, haskell-language-server, +are worth the same check as clangd: gopls, haskell-language-server, typescript-language-server and julia's LanguageServer. ## Related: formatters ship without their tools diff --git a/tests/app/Spec/Tests/R.hs b/tests/app/Spec/Tests/R.hs index dc7d4ca8..350a22e9 100644 --- a/tests/app/Spec/Tests/R.hs +++ b/tests/app/Spec/Tests/R.hs @@ -37,12 +37,13 @@ tests = describe "R" $ introduceNixEnvironment [kernelSpec] [] "R" $ introduceJu VariableInspector.tests "R" it "languageserver formats test.R" $ do - -- styler produces the right answer here -- you can watch it come back over the wire in the - -- session log -- but the R language server then never answers lsp-test's `shutdown`, and - -- the session hangs instead of finishing. Driving the same sequence against the same - -- server in the same sandbox by hand shuts down fine, so it's something about how - -- lsp-test ends the session. Pending until that's sorted out; a hanging test is worse - -- than no test. + -- styler produces the right answer and the server delivers it -- its own debug log shows + -- the shutdown response going out and a clean exit 0. lsp-test never reads that response: + -- a publishDiagnostics notification lands in the window between the shutdown request and + -- its reply, and the session deadlocks in teardown. The 120s timeout around the shutdown + -- exchange doesn't fire; the process sits idle for half an hour on 1.5s of CPU. + -- Replaying the identical bytes against the same server in the same sandbox always gets + -- the response, so this is lsp-test's teardown, not R. _ <- pending formatsAs 180 "languageserver" "test.R" LanguageKind_R "f <- function(a,b){\na+b\n}\n" From 1d1025f9cb6d965258e26be06a81ef4756f8aa93 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 9 Oct 2026 04:55:22 -0700 Subject: [PATCH 29/34] R: unpend the formatting test now that the lsp-test teardown deadlock is fixed --- FEATURE-MATRIX.md | 15 ++++++--------- .../languageserver.nix | 9 +++++++++ tests/app/Spec/Tests/R.hs | 15 +++------------ tests/stack.yaml | 4 ++-- tests/stack.yaml.lock | 16 ++++++++-------- 5 files changed, 28 insertions(+), 31 deletions(-) diff --git a/FEATURE-MATRIX.md b/FEATURE-MATRIX.md index c90f017f..5abd0adb 100644 --- a/FEATURE-MATRIX.md +++ b/FEATURE-MATRIX.md @@ -221,15 +221,12 @@ Caveats the table above can't show: [rust-notebook-language-server#2](https://github.com/codedownio/rust-notebook-language-server/pull/2) fixes it; the Rust formatting test stays pending until that's released and the pinned version here moves up. -- The R formatting test is pending too, and it isn't R's fault. styler produces the right - answer and the server delivers it: with `languageserver::run(debug = …)` turned on, its log - shows the shutdown response going out and a clean exit 0. lsp-test never reads that - response. A `publishDiagnostics` notification arrives in the window between the shutdown - request and its reply — 11 ms before it — and the session deadlocks in teardown. The 120s - timeout around the shutdown exchange never fires; the process sits idle for half an hour on - 1.5 seconds of CPU. Replaying the identical byte sequence against the same server in the - same sandbox always gets the response, including pipelined sends and with stdin closed, so - the fix belongs in lsp-test's teardown path. +- R formatted correctly all along, but the test used to hang. Three things stacked up: + languageserver answers `shutdown` with `"result": []` where the spec says `null`; lsp-test's + reader thread died decoding that, so the response went unread; and the shutdown wait ran + under `uninterruptibleMask` (unliftio runs `finally` cleanups that way), so no timeout could + fire. Fixed in codedownio/lsp#1, plus a `postPatch` here making languageserver's shutdown + response conformant. ## Known gaps diff --git a/modules/kernels/r/language-server-languageserver/languageserver.nix b/modules/kernels/r/language-server-languageserver/languageserver.nix index bf8de641..2c8f02a3 100644 --- a/modules/kernels/r/language-server-languageserver/languageserver.nix +++ b/modules/kernels/r/language-server-languageserver/languageserver.nix @@ -41,6 +41,15 @@ stdenv.mkDerivation { sha256 = "0sifrmkv6hn15nppjg4wjvs3l8an3lsa4rdp3xi3f4w006kdgx5w"; }; + # The LSP spec says a shutdown response carries a null result; languageserver passes + # `list()`, which jsonlite renders as `[]`. Strict clients reject it -- lsp-test fails to + # decode the response entirely, so it never sees the server acknowledge shutdown. + # Worth reporting upstream; drop this once it lands there. + postPatch = '' + substituteInPlace R/handlers-general.R \ + --replace-fail 'Response$new(id = id, result = list())' 'Response$new(id = id, result = NULL)' + ''; + configurePhase = '' runHook preConfigure export R_LIBS_SITE="$R_LIBS_SITE''${R_LIBS_SITE:+:}$out/library" diff --git a/tests/app/Spec/Tests/R.hs b/tests/app/Spec/Tests/R.hs index 350a22e9..0bfbf370 100644 --- a/tests/app/Spec/Tests/R.hs +++ b/tests/app/Spec/Tests/R.hs @@ -36,18 +36,9 @@ tests = describe "R" $ introduceNixEnvironment [kernelSpec] [] "R" $ introduceJu VariableInspector.tests "R" - it "languageserver formats test.R" $ do - -- styler produces the right answer and the server delivers it -- its own debug log shows - -- the shutdown response going out and a clean exit 0. lsp-test never reads that response: - -- a publishDiagnostics notification lands in the window between the shutdown request and - -- its reply, and the session deadlocks in teardown. The 120s timeout around the shutdown - -- exchange doesn't fire; the process sits idle for half an hour on 1.5s of CPU. - -- Replaying the identical bytes against the same server in the same sandbox always gets - -- the response, so this is lsp-test's teardown, not R. - _ <- pending - formatsAs 180 "languageserver" "test.R" LanguageKind_R - "f <- function(a,b){\na+b\n}\n" - "f <- function(a, b) {\n a + b\n}\n" + itFormatsAs "languageserver" "test.R" LanguageKind_R + "f <- function(a,b){\na+b\n}\n" + "f <- function(a, b) {\n a + b\n}\n" main :: IO () diff --git a/tests/stack.yaml b/tests/stack.yaml index 388e9103..d6bc6d06 100644 --- a/tests/stack.yaml +++ b/tests/stack.yaml @@ -19,8 +19,8 @@ extra-deps: - sandwich-contexts - git: https://github.com/codedownio/lsp.git - # upgrade-monads-newest-jan2026 branch - commit: c723f4a4fb99661c76b1356f8a839a0e266b0f32 + # shutdown-deadlock branch, off upgrade-monads-newest-jan2026, for the lsp-test teardown fix + commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 subdirs: - lsp - lsp-test diff --git a/tests/stack.yaml.lock b/tests/stack.yaml.lock index d4203646..88a64328 100644 --- a/tests/stack.yaml.lock +++ b/tests/stack.yaml.lock @@ -31,7 +31,7 @@ packages: git: https://github.com/codedownio/sandwich.git subdir: sandwich-contexts - completed: - commit: c723f4a4fb99661c76b1356f8a839a0e266b0f32 + commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 git: https://github.com/codedownio/lsp.git name: lsp pantry-tree: @@ -40,24 +40,24 @@ packages: subdir: lsp version: 2.7.0.1 original: - commit: c723f4a4fb99661c76b1356f8a839a0e266b0f32 + commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 git: https://github.com/codedownio/lsp.git subdir: lsp - completed: - commit: c723f4a4fb99661c76b1356f8a839a0e266b0f32 + commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 git: https://github.com/codedownio/lsp.git name: lsp-test pantry-tree: - sha256: 46f37966ca753a019a6427850e0b16dc7db1e01068f31d0ae0871d038128a605 - size: 2304 + sha256: 794f872ace5e8f400451bb51e1698b6faed0b4389f473600b82491fdb9313dbb + size: 2305 subdir: lsp-test version: 0.17.1.1 original: - commit: c723f4a4fb99661c76b1356f8a839a0e266b0f32 + commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 git: https://github.com/codedownio/lsp.git subdir: lsp-test - completed: - commit: c723f4a4fb99661c76b1356f8a839a0e266b0f32 + commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 git: https://github.com/codedownio/lsp.git name: lsp-types pantry-tree: @@ -66,7 +66,7 @@ packages: subdir: lsp-types version: 2.3.0.1 original: - commit: c723f4a4fb99661c76b1356f8a839a0e266b0f32 + commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 git: https://github.com/codedownio/lsp.git subdir: lsp-types - completed: From 9c1116ff54530529b27962ed8403d48461afd43e Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 9 Oct 2026 15:08:06 -0700 Subject: [PATCH 30/34] Rust: bump rust-notebook-language-server to 0.2.5.0 and unpend the formatting test --- .../rnls-version.nix | 2 +- .../language_server_rust_analyzer/rnls.nix | 24 +++++++++---------- tests/app/Spec/Tests/Rust/Formatting.hs | 10 +------- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/modules/kernels/rust/language_server_rust_analyzer/rnls-version.nix b/modules/kernels/rust/language_server_rust_analyzer/rnls-version.nix index 167c9d2d..b3860fa1 100644 --- a/modules/kernels/rust/language_server_rust_analyzer/rnls-version.nix +++ b/modules/kernels/rust/language_server_rust_analyzer/rnls-version.nix @@ -1 +1 @@ -"0.2.4.1" +"0.2.5.0" diff --git a/modules/kernels/rust/language_server_rust_analyzer/rnls.nix b/modules/kernels/rust/language_server_rust_analyzer/rnls.nix index 8cfd0efb..da0ca911 100644 --- a/modules/kernels/rust/language_server_rust_analyzer/rnls.nix +++ b/modules/kernels/rust/language_server_rust_analyzer/rnls.nix @@ -21,28 +21,28 @@ { # HASHES_START "aarch64-linux" = fetchzip { - name = "rust-notebook-language-server-0.2.4.1"; + name = "rust-notebook-language-server-0.2.5.0"; stripRoot = false; - url = "https://github.com/codedownio/rust-notebook-language-server/releases/download/v0.2.4.1/rust-notebook-language-server-0.2.4.1-aarch64-linux.tar.gz"; - hash = "sha256-cueCWa2AgDxxgPgEYDj9m9qJA58Kh8rLsB7UR/TfDM8="; + url = "https://github.com/codedownio/rust-notebook-language-server/releases/download/v0.2.5.0/rust-notebook-language-server-0.2.5.0-aarch64-linux.tar.gz"; + hash = "sha256-5Ov2YT5CUcJBQawJtAfZ1uiMhXD78br+0NQ1N1v7zDQ="; }; "x86_64-linux" = fetchzip { - name = "rust-notebook-language-server-0.2.4.1"; + name = "rust-notebook-language-server-0.2.5.0"; stripRoot = false; - url = "https://github.com/codedownio/rust-notebook-language-server/releases/download/v0.2.4.1/rust-notebook-language-server-0.2.4.1-x86_64-linux.tar.gz"; - hash = "sha256-1a9qcTKIYNOcGSo5rh+LnjDj1nb/N84Vyn3NMujfjw0="; + url = "https://github.com/codedownio/rust-notebook-language-server/releases/download/v0.2.5.0/rust-notebook-language-server-0.2.5.0-x86_64-linux.tar.gz"; + hash = "sha256-rOgdwfZNC9oggoWbie8k0RH8KPuw/5g2vzLnjzNvRoI="; }; "x86_64-darwin" = fetchzip { - name = "rust-notebook-language-server-0.2.4.1"; + name = "rust-notebook-language-server-0.2.5.0"; stripRoot = false; - url = "https://github.com/codedownio/rust-notebook-language-server/releases/download/v0.2.4.1/rust-notebook-language-server-0.2.4.1-x86_64-darwin.tar.gz"; - hash = "sha256-xRilifTxdhOP8eep2Lf/dEKhb1nlrEHbh0O8zPU/S/8="; + url = "https://github.com/codedownio/rust-notebook-language-server/releases/download/v0.2.5.0/rust-notebook-language-server-0.2.5.0-x86_64-darwin.tar.gz"; + hash = "sha256-ujgWq/9CgyRamUjvMuhMRJ9xyJqa8wDeGinvaqwpP1Y="; }; "aarch64-darwin" = fetchzip { - name = "rust-notebook-language-server-0.2.4.1"; + name = "rust-notebook-language-server-0.2.5.0"; stripRoot = false; - url = "https://github.com/codedownio/rust-notebook-language-server/releases/download/v0.2.4.1/rust-notebook-language-server-0.2.4.1-aarch64-darwin.tar.gz"; - hash = "sha256-Krsoh3WLG8TkXKTEtFNCThbCc1UQjlziiDngOgvOtF4="; + url = "https://github.com/codedownio/rust-notebook-language-server/releases/download/v0.2.5.0/rust-notebook-language-server-0.2.5.0-aarch64-darwin.tar.gz"; + hash = "sha256-T/N1SropbQ6yhnGhd+wM59JTEftKs6WkH4tKwx9dmyc="; }; # HASHES_END }.${stdenv.hostPlatform.system} diff --git a/tests/app/Spec/Tests/Rust/Formatting.hs b/tests/app/Spec/Tests/Rust/Formatting.hs index ebc9dcd4..ce75d88e 100644 --- a/tests/app/Spec/Tests/Rust/Formatting.hs +++ b/tests/app/Spec/Tests/Rust/Formatting.hs @@ -8,15 +8,7 @@ import TestLib.Types tests :: (LspContext context m, HasNixEnvironment context) => SpecFree context m () tests = describe "Formatting" $ - it "rust-analyzer formats with rustfmt" $ do - -- rust-analyzer itself formats fine now that rustfmt is on its PATH, but - -- rust-notebook-language-server forwards textDocument/formatting with the notebook's URI - -- rather than the shadow file's, so rust-analyzer answers "file not found". Everything - -- else is rewritten, which is why the rest of the LSP tests pass. - -- - -- https://github.com/codedownio/rust-notebook-language-server/pull/2 fixes it; this test - -- can come off pending once that's released and rnls-version.nix moves up. - _ <- pending + it "rust-analyzer formats with rustfmt" $ formatsAs 300 "rust-analyzer" "test.rs" LanguageKind_Rust "fn main(){let x = 1+2;println!(\"{}\",x);}\n" "fn main() {\n let x = 1 + 2;\n println!(\"{}\", x);\n}\n" From fa9bc2fc594e30559bf61ae6631f349f21537718 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 9 Oct 2026 15:28:26 -0700 Subject: [PATCH 31/34] R: build languageserver from our fork of master, with the shutdown response fixed --- .../languageserver.nix | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/modules/kernels/r/language-server-languageserver/languageserver.nix b/modules/kernels/r/language-server-languageserver/languageserver.nix index 2c8f02a3..ff03ff31 100644 --- a/modules/kernels/r/language-server-languageserver/languageserver.nix +++ b/modules/kernels/r/language-server-languageserver/languageserver.nix @@ -8,15 +8,15 @@ }: let + # DESCRIPTION's Imports, minus the ones that ship with R (parallel, tools, utils). languageServerDeps = with rPackages; [ R6 callr collections - desc + digest fs jsonlite lintr - repr roxygen2 stringi styler @@ -32,24 +32,18 @@ in stdenv.mkDerivation { name = "r-custom-languageserver"; - version = "0.3.16"; + version = "0.3.20"; + # Our fork of upstream master, carrying one commit: shutdown answers with a null result + # rather than `[]`, which clients that type the result strictly can't decode. That branch + # is the one to send upstream as a PR; point this back at REditorSupport once it lands. src = fetchFromGitHub { - owner = "REditorSupport"; + owner = "codedownio"; repo = "languageserver"; - rev = "beed0f00563390b3e39a9bb47fc25bf852ec3734"; - sha256 = "0sifrmkv6hn15nppjg4wjvs3l8an3lsa4rdp3xi3f4w006kdgx5w"; + rev = "de6d8ba3b36f5da85f6398c53982c327c3fedacd"; + sha256 = "16y7i1mwk97sq4yyca52v1hpm55aamdyf6yzm90qalh18g8av44g"; }; - # The LSP spec says a shutdown response carries a null result; languageserver passes - # `list()`, which jsonlite renders as `[]`. Strict clients reject it -- lsp-test fails to - # decode the response entirely, so it never sees the server acknowledge shutdown. - # Worth reporting upstream; drop this once it lands there. - postPatch = '' - substituteInPlace R/handlers-general.R \ - --replace-fail 'Response$new(id = id, result = list())' 'Response$new(id = id, result = NULL)' - ''; - configurePhase = '' runHook preConfigure export R_LIBS_SITE="$R_LIBS_SITE''${R_LIBS_SITE:+:}$out/library" From b8494fa4788de365b2ebd8e995163ffbf6958bb4 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 9 Oct 2026 15:51:42 -0700 Subject: [PATCH 32/34] Move the lsp pin back to upgrade-monads-newest-jan2026 now that the teardown fix is merged --- tests/stack.yaml | 4 ++-- tests/stack.yaml.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/stack.yaml b/tests/stack.yaml index d6bc6d06..05383fbd 100644 --- a/tests/stack.yaml +++ b/tests/stack.yaml @@ -19,8 +19,8 @@ extra-deps: - sandwich-contexts - git: https://github.com/codedownio/lsp.git - # shutdown-deadlock branch, off upgrade-monads-newest-jan2026, for the lsp-test teardown fix - commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 + # upgrade-monads-newest-jan2026 branch + commit: 51b1a1b5a3760f88c58619f5eebb0201c00945f8 subdirs: - lsp - lsp-test diff --git a/tests/stack.yaml.lock b/tests/stack.yaml.lock index 88a64328..6ab8ce4e 100644 --- a/tests/stack.yaml.lock +++ b/tests/stack.yaml.lock @@ -31,7 +31,7 @@ packages: git: https://github.com/codedownio/sandwich.git subdir: sandwich-contexts - completed: - commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 + commit: 51b1a1b5a3760f88c58619f5eebb0201c00945f8 git: https://github.com/codedownio/lsp.git name: lsp pantry-tree: @@ -40,24 +40,24 @@ packages: subdir: lsp version: 2.7.0.1 original: - commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 + commit: 51b1a1b5a3760f88c58619f5eebb0201c00945f8 git: https://github.com/codedownio/lsp.git subdir: lsp - completed: - commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 + commit: 51b1a1b5a3760f88c58619f5eebb0201c00945f8 git: https://github.com/codedownio/lsp.git name: lsp-test pantry-tree: - sha256: 794f872ace5e8f400451bb51e1698b6faed0b4389f473600b82491fdb9313dbb - size: 2305 + sha256: db969ba51627f5d07fb4109e7b8e56ee68bb0021d39d1b96612a39a4c0dd67a2 + size: 2304 subdir: lsp-test version: 0.17.1.1 original: - commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 + commit: 51b1a1b5a3760f88c58619f5eebb0201c00945f8 git: https://github.com/codedownio/lsp.git subdir: lsp-test - completed: - commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 + commit: 51b1a1b5a3760f88c58619f5eebb0201c00945f8 git: https://github.com/codedownio/lsp.git name: lsp-types pantry-tree: @@ -66,7 +66,7 @@ packages: subdir: lsp-types version: 2.3.0.1 original: - commit: bbc76a97b72602858c0e663ab16d3db37efc3ec6 + commit: 51b1a1b5a3760f88c58619f5eebb0201c00945f8 git: https://github.com/codedownio/lsp.git subdir: lsp-types - completed: From a1b1e3eaaf5e519ccc24ed3447bb71afce69e4bf Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 9 Oct 2026 16:03:49 -0700 Subject: [PATCH 33/34] Move the lsp-test-helpers pin to main now that the /bin/sh change is merged --- tests/stack.yaml | 4 ++-- tests/stack.yaml.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/stack.yaml b/tests/stack.yaml index 05383fbd..58d85f7b 100644 --- a/tests/stack.yaml +++ b/tests/stack.yaml @@ -27,7 +27,7 @@ extra-deps: - lsp-types - git: https://github.com/codedownio/lsp-test-helpers.git - # kernel-piggyback-and-binsh branch, for the /bin/sh the R language server needs at startup - commit: aef37ba837a82582e84ea06f6e6bb1d168b21b68 + # main branch + commit: 961dddec8eaf2ca4cba9d60426111d5230955bf7 - ex-pool-0.2.1@sha256:c8249338ced27bc4d6395ad9c3069eec394fb111813d6ec736814d095f7e6a24,1293 diff --git a/tests/stack.yaml.lock b/tests/stack.yaml.lock index 6ab8ce4e..c1f4bd3d 100644 --- a/tests/stack.yaml.lock +++ b/tests/stack.yaml.lock @@ -70,7 +70,7 @@ packages: git: https://github.com/codedownio/lsp.git subdir: lsp-types - completed: - commit: aef37ba837a82582e84ea06f6e6bb1d168b21b68 + commit: 961dddec8eaf2ca4cba9d60426111d5230955bf7 git: https://github.com/codedownio/lsp-test-helpers.git name: lsp-test-helpers pantry-tree: @@ -78,7 +78,7 @@ packages: size: 1025 version: 0.1.0.0 original: - commit: aef37ba837a82582e84ea06f6e6bb1d168b21b68 + commit: 961dddec8eaf2ca4cba9d60426111d5230955bf7 git: https://github.com/codedownio/lsp-test-helpers.git - completed: hackage: ex-pool-0.2.1@sha256:c8249338ced27bc4d6395ad9c3069eec394fb111813d6ec736814d095f7e6a24,1293 From f3c0ced8c5c94a7e1a75ccf1349b2d0f7b5c968f Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 9 Oct 2026 16:21:26 -0700 Subject: [PATCH 34/34] Test formatting in notebooks, not just raw files --- plans/notebook-formatting-in-proxies.md | 52 +++++++++++++++++----- tests/app/Spec/Tests/Cpp.hs | 9 ++++ tests/app/Spec/Tests/Go.hs | 26 +++++++++++ tests/app/Spec/Tests/Haskell/Formatting.hs | 11 ++++- tests/app/Spec/Tests/Rust/Formatting.hs | 16 ++++--- tests/src/TestLib/LSP/Formatting.hs | 10 +++++ 6 files changed, 106 insertions(+), 18 deletions(-) diff --git a/plans/notebook-formatting-in-proxies.md b/plans/notebook-formatting-in-proxies.md index d72078db..d029c337 100644 --- a/plans/notebook-formatting-in-proxies.md +++ b/plans/notebook-formatting-in-proxies.md @@ -8,10 +8,28 @@ Four languages reach their language server through a notebook proxy: Rust notebook's cells into a document the server will accept, registers it under a different URI, and rewrites requests and responses between the two coordinate spaces. -None of them handled `textDocument/formatting`, so the request went to the server naming a -document it had never opened and failed. rust-notebook-language-server#2 fixed it for Rust. -The other three still can't format a cell, and the reason isn't the missing handler — it's that -their projections can't be undone once a formatter has rewritten the document. +rust-notebook-language-server#2 fixed formatting for Rust. The tests in +`tests/app/Spec/Tests/` now cover a notebook cell for each of the four, which pins down where +each one actually stands: + +| Proxy | Formatting a cell | +| --- | --- | +| rust | works | +| go | works for a cell of plain statements; fails as soon as the cell has an import | +| cpp | fails: clangd answers InvalidParams, "trying to format non-added document" | +| haskell | fails: HLS gates its formatters on the extension, "ormolu does not support .ipynb filetypes" | + +cpp and haskell fail before the hard part: the request reaches the server naming a document it +never opened (cpp), or one whose extension its formatter plugins reject (haskell). Those are +plumbing bugs. + +Go is the interesting one, and it's what the rest of this document is about. gnls does handle +the request, and for `x:=1+2` / `fmt.Println(x)` it maps gofmt's edits back into the cell +correctly — because every edit stays inside a line that the projection left in place. Add an +import and gofmt gets `import ("fmt")` in statement position and won't parse it +(`expected statement, found 'import'`), because the formatting path doesn't apply the +declaration sifter that the analysis path does. Making it sift would then run into the real +problem: once lines move, the edits can't be mapped back one at a time. ## Why formatting is different from every other request @@ -100,8 +118,11 @@ scratch URI which the proxy has to swallow. ## Effort -- **cpp, go** — port `unproject` to their `Transformer` class, add the scratch-document path with - a minimal wrapper. No markers. Roughly a day each with tests. This is the one worth doing. +- **cpp** — plumbing first: forward the request with the shadow URI. Then port `unproject` to + its `Transformer` class and add the scratch-document path with a minimal wrapper. No markers. +- **go** — the per-edit mapping already works for the easy case, so the question is whether to + keep extending it or switch to the scratch document. The scratch document is the one that + makes the import case work. No markers either way. - **haskell** — the hard one. Even a minimal projection still needs `ExpressionToDeclaration` and `StatementToDeclaration` to make a cell parse, and those rewrite lines in place. They're invertible per line if the rewrite is recorded, but the attribution still has to survive ormolu, @@ -117,9 +138,16 @@ this gets easier. Worth confirming on the codedown side before building anything ## Current state -- Rust: works. rust-notebook-language-server#2, merged. -- C++, Go, Haskell: formatting a cell fails with a server error. Inlay hints are fixed separately - (cpp-notebook-language-server#1, go-notebook-language-server#1, - haskell-notebook-language-server#2). Plain `.rs` / `.go` / `.cpp` / `.hs` files were never - affected — the proxies leave a non-notebook URI alone, so formatting already worked there, and - the tests in `tests/app/Spec/Tests/` cover that path. +- Rust: works, both a plain file and a cell. rust-notebook-language-server#2, merged. +- Go: a cell of plain statements works; a cell with an import is `pending` in `Go.hs`. +- C++, Haskell: a cell is `pending` in `Cpp.hs` and `Haskell/Formatting.hs`, with the server + error each one returns. +- Inlay hints are fixed separately (cpp-notebook-language-server#1, + go-notebook-language-server#1, haskell-notebook-language-server#2). +- Plain `.rs` / `.go` / `.cpp` / `.hs` files were never affected — the proxies leave a + non-notebook URI alone, so formatting already worked there. + +The nine languages without a proxy don't need a notebook formatting test. Their configs set a +non-empty `notebook_suffix`, so codedown hands the server a path like `main.ipynb.rb`; from the +server's point of view that's the same case the `test.rb` test already covers. Only a proxy +language (`notebook_suffix = ""`) sees a bare `.ipynb` URI. diff --git a/tests/app/Spec/Tests/Cpp.hs b/tests/app/Spec/Tests/Cpp.hs index 2c0f0094..0f868f72 100644 --- a/tests/app/Spec/Tests/Cpp.hs +++ b/tests/app/Spec/Tests/Cpp.hs @@ -56,6 +56,15 @@ tests' flavor = describe [i|C++ (#{flavor})|] $ introduceNixEnvironment [kernelS "int main(){int x=1+2;return x;}\n" "int main() {\n int x = 1 + 2;\n return x;\n}\n" + it "clangd formats main.ipynb" $ do + -- cpp-notebook-language-server forwards textDocument/formatting with the notebook's + -- URI rather than the shadow file's, so clangd answers InvalidParams "trying to format + -- non-added document". See plans/notebook-formatting-in-proxies.md. + _ <- pending + formatsAs defaultTimeout lsName "main.ipynb" LanguageKind_CPP + "int x=1+2;\nstd::cout< SpecFree context m () -tests = describe "Formatting" $ +tests = describe "Formatting" $ do itFormatsAs' 300 lsName "Test.hs" LanguageKind_Haskell "module Main where\nmain :: IO ()\nmain = putStrLn \"hi\"\n" "module Main where\n\nmain :: IO ()\nmain = putStrLn \"hi\"\n" + + it "haskell-language-server formats main.ipynb" $ do + -- haskell-notebook-language-server forwards textDocument/formatting with the notebook's + -- URI, and HLS gates its formatters on the file extension: "ormolu does not support + -- .ipynb filetypes". See plans/notebook-formatting-in-proxies.md. + _ <- pending + formatsAs 300 lsName "main.ipynb" LanguageKind_Haskell + "foo :: IO ()\nfoo = putStrLn \"hi\"\n" + "foo :: IO ()\nfoo = putStrLn \"hi\"\n" diff --git a/tests/app/Spec/Tests/Rust/Formatting.hs b/tests/app/Spec/Tests/Rust/Formatting.hs index ce75d88e..578de20a 100644 --- a/tests/app/Spec/Tests/Rust/Formatting.hs +++ b/tests/app/Spec/Tests/Rust/Formatting.hs @@ -7,8 +7,14 @@ import TestLib.Types tests :: (LspContext context m, HasNixEnvironment context) => SpecFree context m () -tests = describe "Formatting" $ - it "rust-analyzer formats with rustfmt" $ - formatsAs 300 "rust-analyzer" "test.rs" LanguageKind_Rust - "fn main(){let x = 1+2;println!(\"{}\",x);}\n" - "fn main() {\n let x = 1 + 2;\n println!(\"{}\", x);\n}\n" +tests = describe "Formatting" $ do + itFormatsAs' 300 "rust-analyzer" "test.rs" LanguageKind_Rust + "fn main(){let x = 1+2;println!(\"{}\",x);}\n" + "fn main() {\n let x = 1 + 2;\n println!(\"{}\", x);\n}\n" + + -- A notebook cell goes through rust-notebook-language-server, which projects it into + -- `fn main() { ... }` and has to undo that on the way back + -- (rust-notebook-language-server#2). + itFormatsAs' 300 "rust-analyzer" "main.ipynb" LanguageKind_Rust + "let x = 1+2;println!(\"{}\",x);\n" + "let x = 1 + 2;\nprintln!(\"{}\", x);\n" diff --git a/tests/src/TestLib/LSP/Formatting.hs b/tests/src/TestLib/LSP/Formatting.hs index 3412ff11..67d92114 100644 --- a/tests/src/TestLib/LSP/Formatting.hs +++ b/tests/src/TestLib/LSP/Formatting.hs @@ -8,8 +8,10 @@ module TestLib.LSP.Formatting ( itFormatsAs , itFormatsAs' + , itFormatsAsLabel , itReformats + , defaultTimeout , formatsAs ) where @@ -38,6 +40,14 @@ itFormatsAs' :: ( itFormatsAs' timeoutSeconds name filename languageKind code expected = itFormats' timeoutSeconds name filename languageKind code (`shouldBe` expected) +-- | 'itFormatsAs' with an explicit test name, for when one server formats the same filename +-- more than once and the derived names would collide. +itFormatsAsLabel :: ( + LspContext ctx m, HasNixEnvironment ctx + ) => String -> Text -> FilePath -> LanguageKind -> Text -> Text -> SpecFree ctx m () +itFormatsAsLabel label name filename languageKind code expected = + it label $ formatsAs defaultTimeout name filename languageKind code expected + -- | Weaker than 'itFormatsAs': only that formatting rewrote the document. For formatters whose -- exact output we haven't pinned down, this still catches a server that advertises formatting -- and can't deliver it.