From 58ccb99e2d314edf0c974117ef1cbbad029881ad Mon Sep 17 00:00:00 2001 From: Gaetan Lepage Date: Mon, 19 Jun 2023 11:32:46 +0200 Subject: [PATCH] plugins/coverage: init + test --- plugins/default.nix | 1 + plugins/utils/coverage.nix | 319 ++++++++++++++++++ tests/test-sources/plugins/utils/coverage.nix | 86 +++++ 3 files changed, 406 insertions(+) create mode 100644 plugins/utils/coverage.nix create mode 100644 tests/test-sources/plugins/utils/coverage.nix diff --git a/plugins/default.nix b/plugins/default.nix index 891a3174..8300b4dd 100644 --- a/plugins/default.nix +++ b/plugins/default.nix @@ -82,6 +82,7 @@ ./utils/comment-nvim.nix ./utils/commentary.nix ./utils/conjure.nix + ./utils/coverage.nix ./utils/cursorline.nix ./utils/easyescape.nix ./utils/endwise.nix diff --git a/plugins/utils/coverage.nix b/plugins/utils/coverage.nix new file mode 100644 index 00000000..bcb76816 --- /dev/null +++ b/plugins/utils/coverage.nix @@ -0,0 +1,319 @@ +{ + pkgs, + config, + lib, + ... +}: +with lib; let + cfg = config.plugins.coverage; + helpers = import ../helpers.nix {inherit lib;}; + + keymapsDef = { + coverage = { + command = ""; + description = "Loads a coverage report and immediately displays the coverage signs."; + }; + load = { + command = "Load"; + description = "Loads a coverage report but does not display the coverage signs."; + }; + show = { + command = "Show"; + description = '' + Shows the coverage signs. + Must call `:Coverage` or `:CoverageLoad` first. + ''; + }; + hide = { + command = "Hide"; + description = '' + Hides the coverage signs. + Must call `:Coverage` or `:CoverageLoad` first. + ''; + }; + toggle = { + command = "Toggle"; + description = '' + Toggles the coverage signs. + Must call `:Coverage` or `:CoverageLoad` first. + ''; + }; + clear = { + command = "Clear"; + description = '' + Unloads the cached coverage signs. + `:Coverage` or `:CoverageLoad` must be called again to relad the data. + ''; + }; + summary = { + command = "Summary"; + description = "Displays a coverage summary report in a floating window."; + }; + }; +in { + options.plugins.coverage = + helpers.extraOptionsOptions + // { + enable = mkEnableOption "nvim-coverage"; + + package = helpers.mkPackageOption "nvim-coverage" pkgs.vimPlugins.nvim-coverage; + + keymapsSilent = mkOption { + type = types.bool; + description = "Whether nvim-coverage keymaps should be silent"; + default = false; + }; + + keymaps = + mapAttrs + ( + optionName: properties: + helpers.mkNullOrOption types.str properties.description + ) + keymapsDef; + + autoReload = helpers.defaultNullOpts.mkBool false '' + If true, the `coverage_file` for a langauge will be watched for changes after executing + `:CoverageLoad` or `coverage.load()`. + The file watcher will be stopped after executing `:CoverageClear` or `coverage.clear()`. + ''; + + autoReloadTimeoutMs = helpers.defaultNullOpts.mkInt 500 '' + The number of milliseconds to wait before auto-reloading coverage after detecting a change. + ''; + + commands = helpers.defaultNullOpts.mkBool true "If true, create commands."; + + highlights = { + covered = + helpers.defaultNullOpts.mkNullable + types.attrs + ''{fg = "#B7F071";}'' + "Highlight group for covered signs."; + + uncovered = + helpers.defaultNullOpts.mkNullable + types.attrs + ''{fg = "#F07178";}'' + "Highlight group for uncovered signs."; + + partial = + helpers.defaultNullOpts.mkNullable + types.attrs + ''{fg = "#AA71F0";}'' + "Highlight group for partial coverage signs."; + + summaryBorder = + helpers.defaultNullOpts.mkNullable + types.attrs + ''{link = "FloatBorder";}'' + "Border highlight group of the summary pop-up."; + + summaryNormal = + helpers.defaultNullOpts.mkNullable + types.attrs + ''{link = "NormalFloat";}'' + "Normal text highlight group of the summary pop-up."; + + summaryCursorLine = + helpers.defaultNullOpts.mkNullable + types.attrs + ''{link = "CursorLine";}'' + "Cursor line highlight group of the summary pop-up."; + + summaryHeader = + helpers.defaultNullOpts.mkNullable + types.attrs + ''{ style = "bold,underline"; sp = "bg"; }'' + "Header text highlight group of the summary pop-up."; + + summaryPass = + helpers.defaultNullOpts.mkNullable + types.attrs + ''{link = "CoverageCovered";}'' + "Pass text highlight group of the summary pop-up."; + + summaryFail = + helpers.defaultNullOpts.mkNullable + types.attrs + ''{link = "CoverageUncovered";}'' + "Fail text highlight group of the summary pop-up."; + }; + + loadCoverageCb = helpers.mkNullOrOption types.str '' + A lua function that will be called when a coverage file is loaded. + + Example: + ``` + function (ftype) + vim.notify("Loaded " .. ftype .. " coverage") + end + ``` + ''; + + signs = + mapAttrs + ( + optionName: { + prettyName ? optionName, + defaults, + }: { + hl = + helpers.defaultNullOpts.mkStr + defaults.hl + "The highlight group used for ${prettyName} signs."; + + text = + helpers.defaultNullOpts.mkStr + defaults.text + "The text used for ${prettyName} signs."; + } + ) + { + covered = { + defaults = { + hl = "CoverageCovered"; + text = "▎"; + }; + }; + uncovered = { + defaults = { + hl = "CoverageUncovered"; + text = "▎"; + }; + }; + partial = { + prettyName = "partial coverage"; + defaults = { + hl = "CoveragePartial"; + text = "▎"; + }; + }; + }; + + signGroup = helpers.defaultNullOpts.mkStr "coverage" '' + Name of the sign group used when placing the signs. + See `:h sign-group`. + ''; + + summary = { + widthPercentage = + helpers.defaultNullOpts.mkNullable + (types.numbers.between 0.0 1.0) + "0.70" + "Width of the pop-up window."; + + heightPercentage = + helpers.defaultNullOpts.mkNullable + (types.numbers.between 0.0 1.0) + "0.50" + "Height of the pop-up window."; + + borders = + mapAttrs + ( + optionName: default: + helpers.defaultNullOpts.mkStr default "" + ) + { + topleft = "╭"; + topright = "╮"; + top = "─"; + left = "│"; + right = "│"; + botleft = "╰"; + botright = "╯"; + bot = "─"; + highlight = "Normal:CoverageSummaryBorder"; + }; + + minCoverage = + helpers.defaultNullOpts.mkNullable + (types.numbers.between 0 100) + "80" + '' + Minimum coverage percentage. + Values below this are highlighted with the fail group, values above are highlighted with + the pass group. + ''; + }; + + lang = helpers.defaultNullOpts.mkNullable types.attrs "see upstream documentation" '' + Each key corresponds with the `filetype` of the language and maps to an attrs of + configuration values that differ. + See plugin documentation for language specific options. + + Example: + ``` + { + python = { + coverage_file = ".coverage"; + coverage_command = "coverage json --fail-under=0 -q -o -"; + }; + ruby = { + coverage_file = "coverage/coverage.json"; + }; + } + ``` + ''; + + lcovFile = + helpers.mkNullOrOption + types.str + "File that the plugin will try to read lcov coverage from."; + }; + + config = let + setupOptions = with cfg; + { + auto_reload = autoReload; + auto_reload_timeout_ms = autoReloadTimeoutMs; + inherit commands; + highlights = with highlights; { + inherit covered uncovered partial; + summary_border = summaryBorder; + summary_normal = summaryNormal; + summary_cursor_line = summaryCursorLine; + summary_header = summaryHeader; + summary_pass = summaryPass; + summary_fail = summaryFail; + }; + load_coverage_cb = helpers.ifNonNull' loadCoverageCb (helpers.mkRaw loadCoverageCb); + inherit signs; + sign_group = signGroup; + summary = with summary; { + width_percentage = widthPercentage; + height_percentage = heightPercentage; + inherit borders; + min_coverage = minCoverage; + }; + inherit lang; + lcov_file = lcovFile; + } + // cfg.extraOptions; + in + mkIf cfg.enable { + extraPlugins = [cfg.package]; + + extraConfigLua = '' + require("coverage").setup(${helpers.toLuaObject setupOptions}) + ''; + + maps.normal = mkMerge ( + mapAttrsToList + ( + optionName: properties: let + key = cfg.keymaps.${optionName}; + in + mkIf (key != null) + { + ${key} = { + action = ":Coverage${properties.command}"; + silent = cfg.keymapsSilent; + }; + } + ) + keymapsDef + ); + }; +} diff --git a/tests/test-sources/plugins/utils/coverage.nix b/tests/test-sources/plugins/utils/coverage.nix new file mode 100644 index 00000000..94f42864 --- /dev/null +++ b/tests/test-sources/plugins/utils/coverage.nix @@ -0,0 +1,86 @@ +{ + empty = { + plugins.coverage.enable = true; + }; + + example = { + plugins.coverage = { + enable = true; + + keymapsSilent = true; + keymaps = { + coverage = "a"; + load = "b"; + show = "c"; + hide = "d"; + toggle = "e"; + clear = "f"; + summary = "g"; + }; + + autoReload = false; + autoReloadTimeoutMs = 500; + commands = true; + highlights = { + covered = {fg = "#B7F071";}; + uncovered = {fg = "#F07178";}; + partial = {fg = "#AA71F0";}; + summaryBorder = {link = "FloatBorder";}; + summaryNormal = {link = "NormalFloat";}; + summaryCursorLine = {link = "CursorLine";}; + summaryHeader = { + style = "bold,underline"; + sp = "bg"; + }; + summaryPass = {link = "CoverageCovered";}; + summaryFail = {link = "CoverageUncovered";}; + }; + loadCoverageCb = '' + function (ftype) + vim.notify("Loaded " .. ftype .. " coverage") + end + ''; + signs = { + covered = { + hl = "CoverageCovered"; + text = "▎"; + }; + uncovered = { + hl = "CoverageUncovered"; + text = "▎"; + }; + partial = { + hl = "CoveragePartial"; + text = "▎"; + }; + }; + signGroup = "coverage"; + summary = { + widthPercentage = 0.70; + heightPercentage = 0.50; + borders = { + topleft = "╭"; + topright = "╮"; + top = "─"; + left = "│"; + right = "│"; + botleft = "╰"; + botright = "╯"; + bot = "─"; + highlight = "Normal:CoverageSummaryBorder"; + }; + minCoverage = 80; + }; + lang = { + python = { + coverage_file = ".coverage"; + coverage_command = "coverage json --fail-under=0 -q -o -"; + }; + ruby = { + coverage_file = "coverage/coverage.json"; + }; + }; + lcovFile = null; + }; + }; +}