diff --git a/home/common.nix b/home/common.nix index b646fc1..be94e66 100644 --- a/home/common.nix +++ b/home/common.nix @@ -39,6 +39,7 @@ unzip zstd atool + nki-kakoune ]; home.sessionVariables = { diff --git a/overlay.nix b/overlay.nix index 17501c1..745c979 100644 --- a/overlay.nix +++ b/overlay.nix @@ -113,6 +113,8 @@ let buildInputs = (with final; lib.optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [ Security SystemConfiguration CoreServices ]) ) ++ (with final; [ libiconv ]); + + meta.mainProgram = "kak-lsp"; }; }; diff --git a/packages/common/nki-kakoune/default.nix b/packages/common/nki-kakoune/default.nix index 2c6a83d..bb65355 100644 --- a/packages/common/nki-kakoune/default.nix +++ b/packages/common/nki-kakoune/default.nix @@ -1,7 +1,23 @@ -{ callPackage, kakoune, ... }: kakoune.override { +{ callPackage, kakoune, kakoune-unwrapped, ... }: +let + lsp = callPackage ./lsp.nix { }; +in +(kakoune.override { plugins = callPackage ./plugins.nix { } ++ callPackage ./themes.nix { } ++ [ - ./kaktex + (callPackage ./kaktex { }) + (callPackage ./rc.nix { }) + lsp.plugin ]; -} +}).overrideAttrs (attrs: { + buildCommand = '' + ${attrs.buildCommand or ""} + # location of kak binary is used to find ../share/kak/autoload, + # unless explicitly overriden with KAKOUNE_RUNTIME + rm "$out/bin/kak" + makeWrapper "${kakoune-unwrapped}/bin/kak" "$out/bin/kak" \ + --set KAKOUNE_RUNTIME "$out/share/kak" \ + --suffix PATH ":" "${lsp.extraPaths}" + ''; +}) diff --git a/packages/common/nki-kakoune/kakrc b/packages/common/nki-kakoune/kakrc new file mode 100644 index 0000000..aafa044 --- /dev/null +++ b/packages/common/nki-kakoune/kakrc @@ -0,0 +1,191 @@ +# Enable kak-tree-sitter +eval %sh{test -z "$WE_STARTED_KAK" && kak-tree-sitter --kakoune -d --server --init $kak_session} +map global normal ": enter-user-mode tree-sitter" +# ## Set some color overrides +# set global kts_yellow "rgb:e2b75e" +# set global kts_teal "rgb:008080" +# set global kts_mauve "rgb:c264ff" +# set global kts_sky "rgb:6aa622" +# Color scheme +colorscheme catppuccin-latte +set global base "default" + +# Set indentation guides +add-highlighter global/indent-guides show-whitespaces -tab " " -spc " " -lf " " -nbsp " " +set-face global Whitespace default,default +set-face global WhitespaceIndent +d@comment + +# Assistant +set global ui_options terminal_assistant=cat + +# Enable line numbers +hook global WinCreate .* %{ + addhl window/number-lines number-lines +} + +set global grepcmd "rg --line-number --no-column --no-heading --color=never " + +# Kitty-specific options +hook -group windowing global KakBegin .* %{ + set global kitty_window_type os-window +} + +# Comment line and block +map global normal <#> ': comment-line' +map global normal ': comment-block' + +# Go to grep-jump +map global goto f -docstring "current grep-jump match" ': grep-jump' + +# System clipboard interactions +hook global RegisterModified '"' %{ nop %sh{ + printf "%s" "$kak_main_reg_dquote" | pbcopy >/dev/null 2>/dev/null & +}} +map global user P -docstring "Paste before cursor from clipboard" '! pbpaste -n | cat' +map global user p -docstring "Paste after cursor from clipboard" ' pbpaste -n | cat' +map global user R -docstring "Replace selection with text from clipboard" '! pbpaste -n | cat' +define-command -params 0 -docstring "Copy line down" copyline %{ + execute-keys -draft 'xy'%val{count}'P' +} +map global normal <+> -docstring "Copy line down" ': copyline' +define-command -params 0 -docstring "Delete current pair of brackets" delete-current-brackets %{ + execute-keys 'm' + execute-keys -draft 'd' + execute-keys 'H' +} +map global normal D ": delete-current-brackets" + +# Disable write-to +# unalias global w +# define-command -params 0 -docstring "Writes the current file" w "write" + +# Tab sizes +hook global InsertChar \t %{ exec -draft -itersel h@ } +set global tabstop 2 +set global indentwidth 2 + +# Language-specific tabstop with override +hook global WinSetOption filetype=(rust) %{ + set window tabstop 4 + set window indentwidth 4 +} + +# Ctrl + a in insert mode = esc +map global insert '' + +# Tab completion +hook global InsertCompletionShow .* %{ + try %{ + # this command temporarily removes cursors preceded by whitespace; + # if there are no cursors left, it raises an error, does not + # continue to execute the mapping commands, and the error is eaten + # by the `try` command so no warning appears. + execute-keys -draft 'h\h' + map window insert + map window insert + } +} +hook global InsertCompletionHide .* %{ + unmap window insert + unmap window insert +} + +# in Insert mode moves to end of line. +map global insert 'A' + +hook global WinSetOption filetype=(fsharp) %{ + set-option window comment_line "//" + # Set up formatting + # set-option window formatcmd "~/.dotnet/tools/fantomas --stdin --stdout" + # hook window -group fsharp-format BufWritePre .* %{ format } +} + +hook global WinSetOption filetype=(ocaml) %{ + unset-option buffer comment_line + set-option buffer comment_block_begin "(*" + set-option buffer comment_block_end "*)" +} + +hook global WinSetOption filetype=(haskell) %{ + set-option buffer makecmd "cabal build" +} + +hook global WinSetOption filetype=(rust) %{ + set-option buffer makecmd "cargo check" +} + +hook global WinSetOption filetype=(scala) %{ + # Format the document if possible + hook -group scala-fmt window BufWritePre .* %{ lsp-formatting-sync } +} + +hook global WinSetOption filetype=(typst) %{ + set-option window comment_line "//" + set-option window comment_block_begin "/*" + set-option window comment_block_end "*/" + + # borrow markdown's hooks + require-module markdown + + hook window ModeChange pop:insert:.* -group markdown-trim-indent markdown-trim-indent + hook window InsertChar \n -group markdown-insert markdown-insert-on-new-line + hook window InsertChar \n -group markdown-indent markdown-indent-on-new-line + hook -once -always window WinSetOption filetype=.* %{ remove-hooks window markdown-.+ } +} + +define-command -params 0 -docstring "Set up build" scala-build-connect %{ + lsp-execute-command 'build-connect' '"[]"' +} + +define-command -params 0 -docstring "Import build" scala-build-import %{ + lsp-execute-command 'build-import' '"[]"' +} + +def -hidden insert-c-n %{ + try %{ + lsp-snippets-select-next-placeholders + exec 'd' + } catch %{ + exec -with-hooks '' + } +} +map global insert ": insert-c-n" + +# Use C++ for .h headers +hook global BufCreate .*[.](h) %{ + set-option buffer filetype cpp +} + +hook global BufCreate .*[.]kakrc %{ + set-option buffer filetype kak +} + +hook global BufCreate .*[.]md %{ + add-highlighter buffer/ wrap +} + +hook global BufCreate .*[.](sc|sbt) %{ + set-option buffer filetype scala +} + +hook global BufCreate .*[.]typ %{ + set-option buffer filetype typst + add-highlighter buffer/ wrap +} + +hook global BufCreate .*[.]templ %{ + set-option buffer filetype templ + set-option buffer comment_line "//" +} + +hook global BufCreate .*[.]hylo %{ + set-option buffer filetype hylo + set-option buffer comment_line "//" +} + +hook global BufOpenFile .* %{ + modeline-parse +} + +map global normal ':inc-dec-modify-numbers + %val{count}' +map global normal ':inc-dec-modify-numbers - %val{count}' diff --git a/packages/common/nki-kakoune/lsp.nix b/packages/common/nki-kakoune/lsp.nix new file mode 100644 index 0000000..3712d6f --- /dev/null +++ b/packages/common/nki-kakoune/lsp.nix @@ -0,0 +1,307 @@ +{ lib +, writeTextDir +, formats +, kak-lsp +, # LSP packages + ccls +, gopls +, nil +, nixpkgs-fmt +, python311Packages +, ltex-ls +, nodePackages +, tailwindcss-language-server +, fsautocomplete +, metals +, texlab +, marksman +, rust-analyzer +, ... +}: +let + # Configuration for kak-lsp + config = { + languageIDs = { + c = "c_cpp"; + cpp = "c_cpp"; + javascript = "javascriptreact"; + typescript = "typescriptreact"; + protobuf = "proto"; + sh = "shellscript"; + }; + + languageServers = + let + vscodeServerOf = name: { + name = "vscode-${name}-language-server"; + value = { + args = [ "--stdio" ]; + command = "vscode-${name}-language-server"; + filetypes = [ name "templ" ]; + roots = [ "package.json" ".git" ]; + }; + package = nodePackages.vscode-langservers-extracted; + }; + in + { + ccls = { + args = [ "-v=2" "-log-file=/tmp/ccls.log" ]; + package = ccls; + command = "ccls"; + filetypes = [ "c" "cpp" ]; + roots = [ "compile_commands.json" ".cquery" ".git" ]; + }; + gopls = { + command = "gopls"; + package = gopls; + filetypes = [ "go" ]; + offset_encoding = "utf-8"; + roots = [ "Gopkg.toml" "go.mod" ".git" ".hg" ]; + settings = { gopls = { hoverKind = "SynopsisDocumentation"; semanticTokens = true; }; }; + settings_section = "gopls"; + }; + haskell-language-server = { + args = [ "--lsp" ]; + command = "haskell-language-server-wrapper"; + filetypes = [ "haskell" ]; + roots = [ "Setup.hs" "stack.yaml" "*.cabal" "package.yaml" ]; + settings_section = "haskell"; + }; + nil = { + command = "nil"; + package = nil; + filetypes = [ "nix" ]; + roots = [ "flake.nix" "shell.nix" ".git" ]; + settings.nil = { + formatting.command = [ "${lib.getExe nixpkgs-fmt}" ]; + }; + }; + pylsp = { + command = "pylsp"; + package = python311Packages.python-lsp-server; + filetypes = [ "python" ]; + offset_encoding = "utf-8"; + roots = [ "requirements.txt" "setup.py" ".git" ".hg" ]; + }; + # Spellchecking server + ltex-ls = { + command = "ltex-ls"; + args = [ "--log-file=/tmp" ]; + filetypes = [ "latex" "typst" ]; + roots = [ "main.tex" "main.typ" ".git" ]; + package = ltex-ls; + }; + tailwind = { + command = "tailwindcss-language-server"; + args = [ "--stdio" ]; + filetypes = [ "html" "css" "javascript" "typescript" "templ" ]; + roots = [ "tailwind.config.{js,cjs,mjs,ts}" "package.json" ".git" ]; + settings_section = "tailwindCSS"; + settings.tailwindCSS = { + validate = "warning"; + userLanguages.templ = "html"; + }; + package = tailwindcss-language-server; + }; + elixir-ls = { + args = [ ]; + command = "elixir-ls"; + filetypes = [ "elixir" ]; + roots = [ "mix.exs" ]; + }; + typescript-language-server = { + args = [ "--stdio" ]; + command = "typescript-language-server"; + filetypes = [ "typescript" "javascript" ]; + roots = [ "package.json" ]; + package = nodePackages.typescript-language-server; + }; + fsautocomplete = { + args = [ "--adaptive-lsp-server-enabled" "--project-graph-enabled" "--source-text-factory" "RoslynSourceText" ]; + command = "fsautocomplete"; + filetypes = [ "fsharp" ]; + roots = [ "*.fsproj" ]; + settings_section = "FSharp"; + settings.FSharp = { + AutomaticWorkspaceInit = true; + }; + package = fsautocomplete; + }; + metals = { + command = "metals"; + filetypes = [ "scala" ]; + roots = [ "build.sbt" "build.sc" ]; + settings_section = "metals"; + settings.metals = { + enableSemanticHighlighting = true; + showInferredType = true; + decorationProvider = true; + inlineDecorationProvider = true; + # From kakoune-lsp's own options + icons = "unicode"; + isHttpEnabled = true; + statusBarProvider = "log-message"; + compilerOptions = { overrideDefFormat = "unicode"; }; + }; + package = metals; + }; + texlab = { + command = "texlab"; + filetypes = [ "latex" ]; + roots = [ "main.tex" "all.tex" ".git" ]; + settings_section = "texlab"; + settings.texlab = { + build.executable = "latexmk"; + build.args = [ "-pdf" "-shell-escape" "-interaction=nonstopmode" "-synctex=1" "%f" ]; + + build.forwardSearchAfter = true; + build.onSave = true; + + # forwardSearch = + # (if pkgs.stdenv.isDarwin then { + # executable = "/Applications/Skim.app/Contents/SharedSupport/displayline"; + # args = [ "-r" "-g" "%l" "%p" "%f" ]; + # } else + # { + # executable = "${pkgs.zathura}/bin/zathura"; + # args = [ "--synctex-forward" "%l:1:%f" "%p" "-x" "${./kaktex} jump %%{input} %%{line} %%{column}" ]; + # }); + }; + package = texlab; + }; + typst-lsp = { + command = "typst-lsp"; + filetypes = [ "typst" ]; + roots = [ "main.typ" ".git" ]; + settings_section = "typst-lsp"; + settings.typst-lsp = { + experimentalFormatterMode = "on"; + }; + }; + marksman = { + command = "marksman"; + filetypes = [ "markdown" ]; + roots = [ ".marksman.toml" ".git" ]; + package = marksman; + }; + rust-analyzer = { + args = [ ]; + command = "rust-analyzer"; + filetypes = [ "rust" ]; + roots = [ "Cargo.toml" ]; + package = rust-analyzer; + }; + + } // (builtins.listToAttrs (builtins.map vscodeServerOf [ "html" "css" "json" ])); + + faces = [ + ## Items + # (Rust) Macros + { face = "attribute"; token = "attribute"; } + { face = "attribute"; token = "derive"; } + { face = "macro"; token = "macro"; } # Function-like Macro + # Keyword and Fixed Tokens + { face = "keyword"; token = "keyword"; } + { face = "operator"; token = "operator"; } + # Functions and Methods + { face = "function"; token = "function"; } + { face = "method"; token = "method"; } + # Constants + { face = "string"; token = "string"; } + { face = "format_specifier"; token = "formatSpecifier"; } + # Variables + { face = "variable"; token = "variable"; modifiers = [ "readonly" ]; } + { face = "mutable_variable"; token = "variable"; } + { face = "module"; token = "namespace"; } + { face = "variable"; token = "type_parameter"; } + { face = "class"; token = "enum"; } + { face = "class"; token = "struct"; } + { face = "class"; token = "trait"; } + { face = "class"; token = "union"; } + { face = "class"; token = "class"; } + + ## Comments + { face = "documentation"; token = "comment"; modifiers = [ "documentation" ]; } + { face = "comment"; token = "comment"; } + + # Typst + { face = "header"; token = "heading"; } + { face = "ts_markup_link_url"; token = "link"; } + { face = "ts_markup_link_uri"; token = "ref"; } + { face = "ts_markup_link_label"; token = "label"; } + { face = "ts_property"; token = "pol"; } + { face = "ts_markup_list_checked"; token = "marker"; } + { face = "ts_constant_builtin_boolean"; token = "bool"; } + { face = "ts_keyword_control"; token = "delim"; } + { face = "ts_number"; token = "text"; modifiers = [ "math" ]; } + { face = "ts_markup_bold"; token = "text"; modifiers = [ "strong" ]; } + { face = "ts_markup_italic"; token = "text"; modifiers = [ "emph" ]; } + ]; + + raw = { + server = { timeout = 1800; }; + snippet_support = false; + verbosity = 255; + }; + }; + + kak-lsp-config = + let + toml = formats.toml { }; + toLspConfig = attrs: builtins.removeAttrs attrs [ "package" ]; + in + toml.generate "kak-lsp.toml" ({ + semantic_tokens.faces = config.faces; + language_server = toLspConfig config.languageServers; + language_ids = config.languageIDs; + } // config.raw); + + serverPackages = + builtins.filter (v: v != null) + (lib.mapAttrsToList (_: serv: serv.package or null) config.languageServers); +in +{ + extraPaths = lib.makeBinPath serverPackages; + plugin = writeTextDir "share/kak/autoload/kak-lsp.kak" '' + hook global KakBegin .* %{ + try %{ + eval %sh{${lib.getExe kak-lsp} --config ${kak-lsp-config} -s $kak_session} + } + + lsp-enable + map window lsp N -docstring "Display the next message request" ": lsp-show-message-request-next" + map window normal ": enter-user-mode lsp" + map window normal ": lsp-hover" + map window normal ": lsp-hover-buffer" + # lsp-auto-hover-insert-mode-enable + set window lsp_hover_anchor true + map global insert ':try lsp-snippets-select-next-placeholders catch %{ execute-keys -with-hooks tab> }' -docstring 'Select next snippet placeholder' + map global object a 'lsp-object' -docstring 'LSP any symbol' + map global object 'lsp-object' -docstring 'LSP any symbol' + map global object f 'lsp-object Function Method' -docstring 'LSP function or method' + map global object t 'lsp-object Class Interface Struct' -docstring 'LSP class interface or struct' + map global object d 'lsp-diagnostic-object --include-warnings' -docstring 'LSP errors and warnings' + map global object D 'lsp-diagnostic-object' -docstring 'LSP errors' + + hook global WinSetOption filetype=(racket|rust|python|go|javascript|typescript|c|cpp|tex|latex|haskell|nix|fsharp|templ) %{ + # Format the document if possible + hook window BufWritePre .* %{ lsp-formatting-sync } + } + + hook global WinSetOption filetype=(rust|scala|fsharp) %{ + # Enable inlay hints + lsp-inlay-hints-enable window + } + + hook global WinSetOption filetype=(rust|go|fsharp|typst|scala) %{ + hook window -group semantic-tokens BufReload .* lsp-semantic-tokens + hook window -group semantic-tokens NormalIdle .* lsp-semantic-tokens + hook window -group semantic-tokens InsertIdle .* lsp-semantic-tokens + hook -once -always window WinSetOption filetype=.* %{ + remove-hooks window semantic-tokens + } + } + } + ''; +} + diff --git a/packages/common/nki-kakoune/rc.nix b/packages/common/nki-kakoune/rc.nix new file mode 100644 index 0000000..fa4c94d --- /dev/null +++ b/packages/common/nki-kakoune/rc.nix @@ -0,0 +1,18 @@ +{ lib, fish, writeScript, writeTextDir, ... }: + +let + source-pwd = writeScript "source-pwd" '' + #!/usr/bin/env ${lib.getExe fish} + + ${builtins.readFile ./source-pwd.fish} + ''; +in +writeTextDir "share/kak/kakrc.local" '' + ${builtins.readFile ./kakrc} + + # Source any settings in the current working directory, + # recursive upwards + evaluate-commands %sh{ + ${source-pwd} + } +'' diff --git a/packages/common/nki-kakoune/source-pwd.fish b/packages/common/nki-kakoune/source-pwd.fish new file mode 100755 index 0000000..aa9aa37 --- /dev/null +++ b/packages/common/nki-kakoune/source-pwd.fish @@ -0,0 +1,14 @@ +if test (pwd) = "/home/natsukagami/.config/kak" + exit 0 +end + +while true + set kakrc (pwd)/.kakrc + if test -f $kakrc + echo source $kakrc + end + if test (pwd) = "/" + exit 0 + end + cd .. +end diff --git a/packages/common/nki-kakoune/themes.nix b/packages/common/nki-kakoune/themes.nix index 5be9a6d..5c24299 100644 --- a/packages/common/nki-kakoune/themes.nix +++ b/packages/common/nki-kakoune/themes.nix @@ -1,9 +1,9 @@ -{ writeScriptDir, ... }: +{ writeTextDir, ... }: let themes = [ - { name = "catppuccin-latte"; src = ./themes/catppucin-latte.kak; } + { name = "catppuccin-latte"; src = ./themes/catppuccin-latte.kak; } ]; - themeToColorscheme = name: src: writeScriptDir "share/kak/colors/${name}.kak" (builtins.readFile src); + themeToColorscheme = { name, src }: writeTextDir "share/kak/colors/${name}.kak" (builtins.readFile src); in builtins.map themeToColorscheme themes diff --git a/packages/common/nki-kakoune/utils.nix b/packages/common/nki-kakoune/utils.nix new file mode 100644 index 0000000..47e7c75 --- /dev/null +++ b/packages/common/nki-kakoune/utils.nix @@ -0,0 +1,5 @@ +{ lib, writeFile, ... }: { + mkFacesScript = name: faces: writeFile "${name}-faces.kak" ( + lib.concatStringsSep "\n" (builtins.attrValues (builtins.mapAttrs (name: face: "face global ${name} \"${face}\"") faces)) + ); +}