diff --git a/plugins/default.nix b/plugins/default.nix index 14b422db..7e02130d 100644 --- a/plugins/default.nix +++ b/plugins/default.nix @@ -63,6 +63,7 @@ ./utils/indent-blankline.nix ./utils/intellitab.nix ./utils/mark-radar.nix + ./utils/neo-tree.nix ./utils/neorg.nix ./utils/notify.nix ./utils/nvim-autopairs.nix diff --git a/plugins/utils/neo-tree.nix b/plugins/utils/neo-tree.nix new file mode 100644 index 00000000..16b4d5d7 --- /dev/null +++ b/plugins/utils/neo-tree.nix @@ -0,0 +1,1125 @@ +{ + pkgs, + config, + lib, + ... +}: +with lib; let + cfg = config.plugins.neo-tree; + helpers = import ../helpers.nix {inherit lib;}; +in { + options.plugins.neo-tree = let + listOfRendererComponents = with types; listOf (either str attrs); + + mkRendererComponentListOption = helpers.defaultNullOpts.mkNullable listOfRendererComponents; + + mkMappingsOption = defaults: + helpers.defaultNullOpts.mkNullable + (with types; attrsOf (either str attrs)) + defaults + "Mapping options"; + + mkWindowMappingsOption = defaults: + helpers.mkCompositeOption "Window options" { + mappings = mkMappingsOption defaults; + }; + in + helpers.extraOptionsOptions + // { + enable = mkEnableOption "neo-tree"; + + package = helpers.mkPackageOption "neo-tree" pkgs.vimPlugins.neo-tree-nvim; + + sources = + helpers.defaultNullOpts.mkNullable (types.listOf types.str) + ''["filesystem" "buffers" "git_status"]'' + '' + If a user has a sources list it will replace this one. + Only sources listed here will be loaded. + You can also add an external source by adding it's name to this list. + The name used here must be the same name you would use in a require() call. + ''; + + addBlankLineAtTop = + helpers.defaultNullOpts.mkBool false + "Add a blank line at the top of the tree."; + + autoCleanAfterSessionRestore = + helpers.defaultNullOpts.mkBool false + "Automatically clean up broken neo-tree buffers saved in sessions"; + + closeIfLastWindow = + helpers.defaultNullOpts.mkBool false + "Close Neo-tree if it is the last window left in the tab"; + + closeFloatsOnEscapeKey = helpers.defaultNullOpts.mkBool true ""; + + defaultSource = helpers.defaultNullOpts.mkStr "filesystem" ""; + + enableDiagnostics = helpers.defaultNullOpts.mkBool true ""; + + enableGitStatus = helpers.defaultNullOpts.mkBool true ""; + + enableModifiedMarkers = + helpers.defaultNullOpts.mkBool true + "Show markers for files with unsaved changes."; + + enableRefreshOnWrite = + helpers.defaultNullOpts.mkBool true + "Refresh the tree when a file is written. Only used if `use_libuv_file_watcher` is false."; + + gitStatusAsync = helpers.defaultNullOpts.mkBool true ""; + + gitStatusAsyncOptions = + helpers.mkCompositeOption + "These options are for people with VERY large git repos" { + batchSize = + helpers.defaultNullOpts.mkInt 1000 + "How many lines of git status results to process at a time"; + + batchDelay = + helpers.defaultNullOpts.mkInt 10 + "delay in ms between batches. Spreads out the workload to let other processes run."; + + maxLines = + helpers.defaultNullOpts.mkInt 10000 + '' + How many lines of git status results to process. Anything after this will be dropped. + Anything before this will be used. + The last items to be processed are the untracked files. + ''; + }; + + hideRootNode = helpers.defaultNullOpts.mkBool false "Hide the root node."; + + retainHiddenRootIndent = + helpers.defaultNullOpts.mkBool false + '' + If the root node is hidden, keep the indentation anyhow. + This is needed if you use expanders because they render in the indent. + ''; + + logLevel = + helpers.defaultNullOpts.mkEnum + ["trace" "debug" "info" "warn" "error" "fatal"] "info" ""; + + logToFile = + helpers.defaultNullOpts.mkNullable (types.either types.bool types.str) + "false" + "use :NeoTreeLogs to show the file"; + + openFilesInLastWindow = + helpers.defaultNullOpts.mkBool true + "If `false`, open files in top left window"; + + popupBorderStyle = + helpers.defaultNullOpts.mkEnum + ["NC" "double" "none" "rounded" "shadow" "single" "solid"] "NC" ""; + + resizeTimerInterval = + helpers.defaultNullOpts.mkInt 500 + '' + In ms, needed for containers to redraw right aligned and faded content. + Set to -1 to disable the resize timer entirely. + + NOTE: this will speed up to 50 ms for 1 second following a resize + ''; + + sortCaseInsensitive = + helpers.defaultNullOpts.mkBool false + "Used when sorting files and directories in the tree"; + + sortFunction = + helpers.defaultNullOpts.mkStr "nil" + "Uses a custom function for sorting files and directories in the tree"; + + usePopupsForInput = + helpers.defaultNullOpts.mkBool true + "If false, inputs will use vim.ui.input() instead of custom floats."; + + useDefaultMappings = helpers.defaultNullOpts.mkBool true ""; + + sourceSelector = + helpers.mkCompositeOption + "sourceSelector provides clickable tabs to switch between sources." + { + winbar = + helpers.defaultNullOpts.mkBool false + "toggle to show selector on winbar"; + + statusline = + helpers.defaultNullOpts.mkBool false + "toggle to show selector on statusline"; + + showScrolledOffParentNode = + helpers.defaultNullOpts.mkBool false + '' + This will replace the tabs with the parent path of the top visible node when scrolled + down. + ''; + + tabLabels = helpers.mkCompositeOption "falls back to sourceName if null" { + filesystem = helpers.defaultNullOpts.mkStr "  Files " "tab label for files"; + buffers = helpers.defaultNullOpts.mkStr "  Buffers " "tab label for buffers"; + gitStatus = helpers.defaultNullOpts.mkStr "  Git " "tab label for git status"; + diagnostics = + helpers.defaultNullOpts.mkStr " 裂Diagnostics " + "tab label for diagnostics"; + }; + + contentLayout = + helpers.defaultNullOpts.mkEnumFirstDefault ["start" "end" "focus"] + '' + Only with `tabsLayout` = "equal" or "focus". + + - start : |/ 裡 bufname \/... + - end : |/ 裡 bufname \/... + - center : |/ 裡 bufname \/... + ''; + + tabsLayout = + helpers.defaultNullOpts.mkEnum + ["start" "end" "center" "equal" "focus"] + "equal" + '' + - start : |/ a \/ b \/ c \ | + - end : | / a \/ b \/ c \| + - center : | / a \/ b \/ c \ | + - equal : |/ a \/ b \/ c \| + - active : |/ focused tab \/ b \/ c \| + ''; + + truncationCharacter = + helpers.defaultNullOpts.mkStr "…" + "Character to use when truncating the tab label"; + + tabsMinWidth = + helpers.defaultNullOpts.mkNullable types.int "null" + "If int padding is added based on `contentLayout`"; + + tabsMaxWidth = + helpers.defaultNullOpts.mkNullable types.int "null" + "This will truncate text even if `textTruncToFit = false`"; + + padding = + helpers.defaultNullOpts.mkNullable + (with types; either int (attrsOf int)) + "0" + '' + Can be int or table"; + + Example: `{ left = 2; right = 0; }` + ''; + + separator = + helpers.defaultNullOpts.mkNullable + ( + with types; + either str (submodule { + options = { + left = helpers.defaultNullOpts.mkStr "▏" ""; + right = helpers.defaultNullOpts.mkStr "\\" ""; + override = helpers.defaultNullOpts.mkStr null ""; + }; + }) + ) + "Can be a string or a table" + ''{ left = "▏"; right= "▕"; }''; + + separatorActive = + helpers.defaultNullOpts.mkNullable + ( + with types; + either str (submodule { + options = { + left = helpers.mkNullOrOption types.str ""; + right = helpers.mkNullOrOption types.str ""; + override = helpers.mkNullOrOption types.str ""; + }; + }) + ) + '' + Set separators around the active tab. + null falls back to `sourceSelector.separator`. + '' + "null"; + + showSeparatorOnEdge = + helpers.defaultNullOpts.mkBool false + '' + true : |/ a \/ b \/ c \| + false : | a \/ b \/ c | + ''; + + highlightTab = helpers.defaultNullOpts.mkStr "NeoTreeTabInactive" ""; + + highlightTabActive = helpers.defaultNullOpts.mkStr "NeoTreeTabActive" ""; + + highlightBackground = helpers.defaultNullOpts.mkStr "NeoTreeTabInactive" ""; + + highlightSeparator = helpers.defaultNullOpts.mkStr "NeoTreeTabSeparatorInactive" ""; + + highlightSeparatorActive = helpers.defaultNullOpts.mkStr "NeoTreeTabSeparatorActive" ""; + }; + eventHandlers = + helpers.mkNullOrOption + (types.attrsOf types.str) + '' + Configuration of event handlers. + Attrs: + - keys are the events (e.g. `before_render`, `file_opened`) + - values are lua code defining the callback function. + + Example: + ``` + { + before_render = \'\' + function (state) + -- add something to the state that can be used by custom components + end + \'\'; + + file_opened = \'\' + function(file_path) + --auto close + require("neo-tree").close_all() + end + \'\'; + } + ``` + ''; + + defaultComponentConfigs = helpers.mkCompositeOption "Configuration for default components." { + container = helpers.mkCompositeOption "Container options" { + enableCharacterFade = helpers.defaultNullOpts.mkBool true ""; + + width = helpers.defaultNullOpts.mkStr "100%" ""; + + rightPadding = helpers.defaultNullOpts.mkInt 0 ""; + }; + + diagnostics = helpers.mkCompositeOption "diagnostics" { + symbols = helpers.mkCompositeOption "symbols" { + hint = helpers.defaultNullOpts.mkStr "H" ""; + + info = helpers.defaultNullOpts.mkStr "I" ""; + + warn = helpers.defaultNullOpts.mkStr "!" ""; + + error = helpers.defaultNullOpts.mkStr "X" ""; + }; + + highlights = helpers.mkCompositeOption "highlights" { + hint = helpers.defaultNullOpts.mkStr "DiagnosticSignHint" ""; + + info = helpers.defaultNullOpts.mkStr "DiagnosticSignInfo" ""; + + warn = helpers.defaultNullOpts.mkStr "DiagnosticSignWarn" ""; + + error = helpers.defaultNullOpts.mkStr "DiagnosticSignError" ""; + }; + }; + + indent = helpers.mkCompositeOption "indent" { + indentSize = helpers.defaultNullOpts.mkInt 2 ""; + + padding = helpers.defaultNullOpts.mkInt 1 ""; + + withMarkers = helpers.defaultNullOpts.mkBool true ""; + + indentMarker = helpers.defaultNullOpts.mkStr "│" ""; + + lastIndentMarker = helpers.defaultNullOpts.mkStr "└" ""; + + highlight = helpers.defaultNullOpts.mkStr "NeoTreeIndentMarker" ""; + + withExpanders = + helpers.defaultNullOpts.mkNullable types.bool "null" + "If null and file nesting is enabled, will enable expanders."; + + expanderCollapsed = helpers.defaultNullOpts.mkStr "" ""; + + expanderExpanded = helpers.defaultNullOpts.mkStr "" ""; + + expanderHighlight = helpers.defaultNullOpts.mkStr "NeoTreeExpander" ""; + }; + + icon = { + folderClosed = helpers.defaultNullOpts.mkStr "" ""; + + folderOpen = helpers.defaultNullOpts.mkStr "" ""; + + folderEmpty = helpers.defaultNullOpts.mkStr "ﰊ" ""; + + folderEmptyOpen = helpers.defaultNullOpts.mkStr "ﰊ" ""; + + default = helpers.defaultNullOpts.mkStr "*" '' + Only a fallback, if you use nvim-web-devicons and configure default icons there + then this will never be used. + ''; + + highlight = helpers.defaultNullOpts.mkStr "NeoTreeFileIcon" '' + Only a fallback, if you use nvim-web-devicons and configure default icons there + then this will never be used. + ''; + }; + + modified = helpers.mkCompositeOption "modified" { + symbol = helpers.defaultNullOpts.mkStr "[+] " ""; + + highlight = helpers.defaultNullOpts.mkStr "NeoTreeModified" ""; + }; + + name = helpers.mkCompositeOption "name" { + trailingSlash = helpers.defaultNullOpts.mkBool false ""; + + useGitStatusColors = helpers.defaultNullOpts.mkBool true ""; + + highlight = helpers.defaultNullOpts.mkStr "NeoTreeFileName" ""; + }; + + gitStatus = helpers.mkCompositeOption "git status" { + symbols = + helpers.mkCompositeOption + '' + Symbol characters for git status. + Set to empty string to not show them. + '' + ( + mapAttrs + ( + optionName: default: + helpers.defaultNullOpts.mkStr default optionName + ) + { + added = "✚"; + deleted = "✖"; + modified = ""; + renamed = ""; + untracked = ""; + ignored = ""; + unstaged = ""; + staged = ""; + conflict = ""; + } + ); + + align = helpers.defaultNullOpts.mkStr "right" "icon alignment"; + }; + }; + + renderers = helpers.mkCompositeOption "Renderers configuration" { + directory = + mkRendererComponentListOption + '' + [ + "indent" + "icon" + "current_filter" + { + name = "container"; + content = [ + { + name = "name"; + zindex = 10; + } + { + name = "clipboard"; + zindex = 10; + } + { + name = "diagnostics"; + errors_only = true; + zindex = 20; + align = "right"; + hide_when_expanded = true; + } + { + name = "git_status"; + zindex = 20; + align = "right"; + hide_when_expanded = true; + } + ]; + } + ] + '' + "directory renderers"; + + file = + mkRendererComponentListOption + '' + [ + "indent" + "icon" + { + name = "container"; + content = [ + { + name = "name"; + zindex = 10; + } + { + name = "clipboard"; + zindex = 10; + } + { + name = "bufnr"; + zindex = 10; + } + { + name = "modified"; + zindex = 20; + align = "right"; + } + { + name = "diagnostics"; + zindex = 20; + align = "right"; + } + { + name = "git_status"; + zindex = 20; + align = "right"; + } + ]; + } + ] + '' + "file renderers"; + + message = + mkRendererComponentListOption + '' + [ + { + name = "indent"; + with_markers = false; + } + { + name = "name"; + highlight = "NeoTreeMessage"; + } + ] + '' + "message renderers"; + + terminal = + mkRendererComponentListOption + '' + [ + "indent" + "icon" + "name" + "bufnr" + ] + '' + "message renderers"; + }; + + nestingRules = + helpers.defaultNullOpts.mkNullable (types.attrsOf types.str) "{}" + "nesting rules"; + + window = + helpers.mkCompositeOption + '' + Window options. + See https://github.com/MunifTanjim/nui.nvim/tree/main/lua/nui/popup for possible options. + These can also be functions that return these options. + '' + { + position = + helpers.defaultNullOpts.mkEnum + ["left" "right" "top" "bottom" "float" "current"] "left" "position"; + + width = helpers.defaultNullOpts.mkInt 40 "Applies to left and right positions"; + + height = helpers.defaultNullOpts.mkInt 15 "Applies to top and bottom positions"; + + autoExpandWidth = + helpers.defaultNullOpts.mkBool false + '' + Expand the window when file exceeds the window width. does not work with + position = "float" + ''; + + popup = helpers.mkCompositeOption "Settings that apply to float position only" { + size = helpers.mkCompositeOption "size" { + height = helpers.defaultNullOpts.mkStr "80%" "height"; + + width = helpers.defaultNullOpts.mkStr "50%" "height"; + }; + + position = helpers.defaultNullOpts.mkStr "80%" '' + 50% means center it. + You can also specify border here, if you want a different setting from the global + `popupBorderStyle`. + ''; + }; + + sameLevel = helpers.defaultNullOpts.mkBool false '' + Create and paste/move files/directories on the same level as the directory under cursor + (as opposed to within the directory under cursor). + ''; + + insertAs = helpers.defaultNullOpts.mkEnumFirstDefault ["child" "sibling"] '' + Affects how nodes get inserted into the tree during creation/pasting/moving of files if + the node under the cursor is a directory: + + - "child": Insert nodes as children of the directory under cursor. + - "sibling": Insert nodes as siblings of the directory under cursor. + ''; + + mappingOptions = helpers.mkCompositeOption "Mapping options" { + noremap = helpers.defaultNullOpts.mkBool true "noremap"; + + nowait = helpers.defaultNullOpts.mkBool true "nowait"; + }; + + mappings = mkMappingsOption '' + { + "" = { + command = "toggle_node"; + # disable `nowait` if you have existing combos starting with this char that you want to use + nowait = false; + }; + "<2-LeftMouse>" = "open"; + "" = "open"; + "" = "revert_preview"; + P = { + command = "toggle_preview"; + config = { use_float = true; }; + }; + l = "focus_preview"; + S = "open_split"; + # S = "split_with_window_picker"; + s = "open_vsplit"; + # s = "vsplit_with_window_picker"; + t = "open_tabnew"; + # "" = "open_drop"; + # t = "open_tab_drop"; + w = "open_with_window_picker"; + C = "close_node"; + z = "close_all_nodes"; + # Z = "expand_all_nodes"; + R = "refresh"; + a = { + command = "add"; + # some commands may take optional config options, see `:h neo-tree-mappings` for details + config = { + show_path = "none"; # "none", "relative", "absolute" + }; + }; + A = "add_directory"; # also accepts the config.show_path and config.insert_as options. + d = "delete"; + r = "rename"; + y = "copy_to_clipboard"; + x = "cut_to_clipboard"; + p = "paste_from_clipboard"; + c = "copy"; # takes text input for destination, also accepts the config.show_path and config.insert_as options + m = "move"; # takes text input for destination, also accepts the config.show_path and config.insert_as options + e = "toggle_auto_expand_width"; + q = "close_window"; + "?" = "show_help"; + "<" = "prev_source"; + ">" = "next_source"; + } + ''; + }; + filesystem = helpers.mkCompositeOption "Filesystem options" { + window = mkWindowMappingsOption '' + { + H = "toggle_hidden"; + "/" = "fuzzy_finder"; + D = "fuzzy_finder_directory"; + # "/" = "filter_as_you_type"; # this was the default until v1.28 + "#" = "fuzzy_sorter"; # fuzzy sorting using the fzy algorithm + # D = "fuzzy_sorter_directory"; + f = "filter_on_submit"; + "" = "clear_filter"; + "" = "navigate_up"; + "." = "set_root"; + "[g" = "prev_git_modified"; + "]g" = "next_git_modified"; + } + ''; + asyncDirectoryScan = helpers.defaultNullOpts.mkEnumFirstDefault ["auto" "always" "never"] '' + - "auto" means refreshes are async, but it's synchronous when called from the Neotree + commands. + - "always" means directory scans are always async. + - "never" means directory scans are never async. + ''; + + scanMode = helpers.defaultNullOpts.mkEnumFirstDefault ["shallow" "deep"] '' + - "shallow": Don't scan into directories to detect possible empty directory a priori. + - "deep": Scan into directories to detect empty or grouped empty directories a priori. + ''; + + bindToCwd = + helpers.defaultNullOpts.mkBool true + "true creates a 2-way binding between vim's cwd and neo-tree's root."; + + cwdTarget = helpers.mkCompositeOption "cwd target" { + sidebar = helpers.defaultNullOpts.mkStr "tab" "sidebar is when position = left or right"; + current = helpers.defaultNullOpts.mkStr "window" "current is when position = current"; + }; + + filteredItems = helpers.mkCompositeOption "filtered items" { + visible = + helpers.defaultNullOpts.mkBool false + "when true, they will just be displayed differently than normal items"; + + forceVisibleInEmptyFolder = + helpers.defaultNullOpts.mkBool false + "when true, hidden files will be shown if the root folder is otherwise empty"; + + showHiddenCount = + helpers.defaultNullOpts.mkBool true + "when true, the number of hidden items in each folder will be shown as the last entry"; + + hideDotfiles = helpers.defaultNullOpts.mkBool true "hide dotfiles"; + + hideGitignored = helpers.defaultNullOpts.mkBool true "hide gitignored files"; + + hideHidden = + helpers.defaultNullOpts.mkBool true + "only works on Windows for hidden files/directories"; + + hideByName = + helpers.defaultNullOpts.mkNullable (types.listOf types.str) '' + [ + ".DS_Store" + "thumbs.db" + # "node_modules" + ] + '' + "hide by name"; + + hideByPattern = + helpers.defaultNullOpts.mkNullable (types.listOf types.str) '' + [ + # "*.meta" + # "*/src/*/tsconfig.json" + ] + '' + "hide by pattern"; + + alwaysShow = + helpers.defaultNullOpts.mkNullable (types.listOf types.str) '' + [ + # ".gitignored" + ] + '' + "files/folders to always show"; + + neverShow = + helpers.defaultNullOpts.mkNullable (types.listOf types.str) '' + [ + # ".DS_Store" + # "thumbs.db" + ] + '' + "files/folders to never show"; + + neverShowByPattern = + helpers.defaultNullOpts.mkNullable (types.listOf types.str) '' + [ + # ".null-ls_*" + ] + '' + "files/folders to never show (by pattern)"; + }; + + findByFullPathWords = helpers.defaultNullOpts.mkBool false '' + `false` means it only searches the tail of a path. + `true` will change the filter into a full path + + search with space as an implicit ".*", so `fi init` will match: + `./sources/filesystem/init.lua + ''; + + findCommand = + helpers.defaultNullOpts.mkStr "fd" + "This is determined automatically, you probably don't need to set it"; + + findArgs = + helpers.mkNullOrOption + (types.either types.str (types.submodule { + options = { + fd = + helpers.defaultNullOpts.mkNullable (types.listOf types.str) + '' + [ + "--exclude" + ".git" + "--exclude" + "node_modules" + ] + '' + "You can specify extra args to pass to the find command."; + }; + })) + '' + Find arguments + + Either use a list of strings: + + ``` + findArgs = { + fd = [ + "--exclude" + ".git" + "--exclude" + "node_modules" + ]; + }; + ``` + + Or use a function instead of list of strings + ``` + findArgs = \'\' + find_args = function(cmd, path, search_term, args) + if cmd ~= "fd" then + return args + end + --maybe you want to force the filter to always include hidden files: + table.insert(args, "--hidden") + -- but no one ever wants to see .git files + table.insert(args, "--exclude") + table.insert(args, ".git") + -- or node_modules + table.insert(args, "--exclude") + table.insert(args, "node_modules") + --here is where it pays to use the function, you can exclude more for + --short search terms, or vary based on the directory + if string.len(search_term) < 4 and path == "/home/cseickel" then + table.insert(args, "--exclude") + table.insert(args, "Library") + end + return args + end + \'\'; + ``` + ''; + + groupEmptyDirs = + helpers.defaultNullOpts.mkBool false + "when true, empty folders will be grouped together"; + + searchLimit = + helpers.defaultNullOpts.mkInt 50 + "max number of search results when using filters"; + + followCurrentFile = helpers.defaultNullOpts.mkBool false '' + This will find and focus the file in the active buffer every time the current file is + changed while the tree is open. + ''; + + hijackNetrwBehavior = + helpers.defaultNullOpts.mkEnumFirstDefault + ["open_default" "open_current" "disabled"] + '' + - "open_default": netrw disabled, opening a directory opens neo-tree in whatever + position is specified in window.position + - "open_current": netrw disabled, opening a directory opens within the window like netrw + would, regardless of window.position + - "disabled": netrw left alone, neo-tree does not handle opening dirs + ''; + + useLibuvFileWatcher = helpers.defaultNullOpts.mkBool false '' + This will use the OS level file watchers to detect changes instead of relying on nvim + autocmd events. + ''; + }; + + buffers = helpers.mkCompositeOption "Buffers options" { + bindToCwd = helpers.defaultNullOpts.mkBool true "Bind to current working directory."; + + followCurrentFile = helpers.defaultNullOpts.mkBool true '' + This will find and focus the file in the active buffer every time the current file is + changed while the tree is open. + ''; + + groupEmptyDirs = + helpers.defaultNullOpts.mkBool true + "When true, empty directories will be grouped together."; + + window = mkWindowMappingsOption '' + { + "" = "navigate_up"; + "." = "set_root"; + bd = "buffer_delete"; + } + ''; + }; + + gitStatus = helpers.mkCompositeOption "git status options" { + window = mkWindowMappingsOption '' + { + A = "git_add_all"; + gu = "git_unstage_file"; + ga = "git_add_file"; + gr = "git_revert_file"; + gc = "git_commit"; + gp = "git_push"; + gg = "git_commit_and_push"; + } + ''; + }; + + example = helpers.mkCompositeOption "example options" { + renderers = helpers.mkCompositeOption "renderers" { + custom = + mkRendererComponentListOption + '' + [ + "indent" + { + name = "icon"; + default = "C"; + } + "custom" + "name" + ] + '' + "custom renderers"; + }; + + window = mkWindowMappingsOption '' + { + "" = "toggle_node"; + "" = "example_command"; + d = "show_debug_info"; + } + ''; + }; + }; + + config = let + inherit (helpers) ifNonNull' mkRaw; + + processRendererComponent = component: + if isString component + then [component] + else + (mapAttrs' ( + name: value: { + name = + if name == "name" + then "@" + else name; + value = + if isList value + then processRendererComponentList value + else value; + } + ) + component); + + processRendererComponentList = componentList: + ifNonNull' componentList + (map processRendererComponent componentList); + + processMapping = key: action: + if isString action + then action + else + mapAttrs' (k: v: { + name = + if k == "command" + then "@" + else k; + value = v; + }) + action; + + processMappings = mappings: + ifNonNull' mappings + (mapAttrs processMapping mappings); + + processWindowMappings = window: + ifNonNull' window { + mappings = processMappings window; + }; + + options = with cfg; + { + inherit (cfg) sources; + add_blank_line_at_top = cfg.addBlankLineAtTop; + auto_clean_after_session_restore = cfg.autoCleanAfterSessionRestore; + close_if_last_window = cfg.closeIfLastWindow; + close_floats_on_escape_key = cfg.closeFloatsOnEscapeKey; + default_source = cfg.defaultSource; + enable_diagnostics = cfg.enableDiagnostics; + enable_git_status = cfg.enableGitStatus; + enable_modified_markers = cfg.enableModifiedMarkers; + enable_refresh_on_write = cfg.enableRefreshOnWrite; + git_status_async = cfg.gitStatusAsync; + git_status_async_options = ifNonNull' cfg.gitStatusAsyncOptions { + batch_size = cfg.gitStatusAsyncOptions.batchSize; + batch_delay = cfg.gitStatusAsyncOptions.batchDelay; + max_lines = cfg.gitStatusAsyncOptions.maxLines; + }; + hide_root_node = cfg.hideRootNode; + retain_hidden_root_indent = cfg.retainHiddenRootIndent; + log_level = cfg.logLevel; + log_to_file = cfg.logToFile; + open_files_in_last_window = cfg.openFilesInLastWindow; + popup_border_style = cfg.popupBorderStyle; + resize_timer_interval = cfg.resizeTimerInterval; + sort_case_insensitive = cfg.sortCaseInsensitive; + sort_function = + ifNonNull' cfg.sortFunction + (mkRaw cfg.sortFunction); + use_popups_for_input = cfg.usePopupsForInput; + use_default_mappings = cfg.useDefaultMappings; + source_selector = with cfg.sourceSelector; + ifNonNull' cfg.sourceSelector { + inherit winbar statusline; + show_scrolled_off_parent_node = showScrolledOffParentNode; + tab_labels = with tabLabels; + ifNonNull' cfg.sourceSelector.tabLabels { + inherit filesystem buffers diagnostics; + git_status = gitStatus; + }; + content_layout = contentLayout; + tabs_layout = tabsLayout; + truncation_character = truncationCharacter; + tabs_min_width = tabsMinWidth; + tabs_max_width = tabsMaxWidth; + inherit padding separator; + separator_active = separatorActive; + show_separator_on_edge = showSeparatorOnEdge; + highlight_tab = highlightTab; + highlight_tab_active = highlightTabActive; + highlight_background = highlightBackground; + highlight_separator = highlightSeparator; + highlight_separator_active = highlightSeparatorActive; + }; + event_handlers = + ifNonNull' cfg.eventHandlers + ( + mapAttrsToList + (event: handler: { + inherit event; + handler = helpers.mkRaw handler; + }) + cfg.eventHandlers + ); + default_component_configs = with cfg.defaultComponentConfigs; + ifNonNull' cfg.defaultComponentConfigs { + container = ifNonNull' cfg.defaultComponentConfigs.container { + enable_character_fade = container.enableCharacterFade; + inherit (container) width; + right_padding = container.rightPadding; + }; + inherit diagnostics; + indent = with indent; + ifNonNull' cfg.defaultComponentConfigs.indent { + indent_size = indentSize; + inherit padding; + with_markers = withMarkers; + indent_markers = indentMarker; + last_indent_marker = lastIndentMarker; + inherit highlight; + with_expanders = withExpanders; + expander_collapsed = expanderCollapsed; + expander_expanded = expanderExpanded; + expander_highlight = expanderHighlight; + }; + icon = with icon; + ifNonNull' cfg.defaultComponentConfigs.icon { + folder_closed = folderClosed; + folder_open = folderOpen; + folder_empty = folderEmpty; + folder_empty_open = folderEmptyOpen; + inherit default highlight; + }; + inherit modified; + name = with name; + ifNonNull' cfg.defaultComponentConfigs.name { + trailing_slash = trailingSlash; + use_git_status_colors = useGitStatusColors; + inherit highlight; + }; + git_status = gitStatus; + }; + renderers = ifNonNull' cfg.renderers ( + mapAttrs (name: processRendererComponentList) cfg.renderers + ); + nesting_rules = cfg.nestingRules; + window = with window; + ifNonNull' cfg.window { + inherit position width height; + auto_expand_width = autoExpandWidth; + inherit popup; + same_level = sameLevel; + insert_as = insertAs; + mapping_options = mappingOptions; + mappings = processMappings mappings; + }; + filesystem = with filesystem; + ifNonNull' cfg.filesystem { + window = processWindowMappings window; + async_directory_scan = asyncDirectoryScan; + scan_mode = scanMode; + bind_to_cwd = bindToCwd; + cwd_target = cwdTarget; + filteredItems = with filteredItems; + ifNonNull' cfg.filesystem.filteredItems { + inherit visible; + force_visible_in_empty_folder = forceVisibleInEmptyFolder; + show_hidden_count = showHiddenCount; + hide_dotfiles = hideDotfiles; + hide_gitignored = hideGitignored; + hide_hidden = hideHidden; + hide_by_name = hideByName; + hide_by_pattern = hideByPattern; + always_show = alwaysShow; + never_show = neverShow; + never_show_by_pattern = neverShowByPattern; + }; + find_by_full_path_words = findByFullPathWords; + find_command = findCommand; + find_args = + if isString findArgs + then mkRaw findArgs + else findArgs; + group_empty_dirs = groupEmptyDirs; + search_limit = searchLimit; + follow_current_file = followCurrentFile; + hijack_netrw_behavior = hijackNetrwBehavior; + use_libuv_file_watcher = useLibuvFileWatcher; + }; + buffers = with buffers; + ifNonNull' cfg.buffers { + bind_to_cwd = bindToCwd; + follow_current_file = followCurrentFile; + group_empty_dirs = groupEmptyDirs; + window = processWindowMappings window; + }; + git_status = ifNonNull' cfg.gitStatus { + window = processWindowMappings cfg.gitStatus.window; + }; + example = with example; + ifNonNull' cfg.example { + renderers = ifNonNull' cfg.example.renderers { + custom = processRendererComponentList renderers.custom; + }; + }; + } + // cfg.extraOptions; + in + mkIf cfg.enable { + extraPlugins = with pkgs.vimPlugins; [ + cfg.package + nvim-web-devicons + ]; + + extraConfigLua = '' + require('neo-tree').setup(${helpers.toLuaObject options}) + ''; + extraPackages = [pkgs.git]; + }; +} diff --git a/tests/plugins/neo-tree.nix b/tests/plugins/neo-tree.nix new file mode 100644 index 00000000..9748186f --- /dev/null +++ b/tests/plugins/neo-tree.nix @@ -0,0 +1,402 @@ +{ + # Empty configuration + empty = { + plugins.neo-tree.enable = true; + }; + + # All the upstream default options of neo-tree + defaults = { + plugins.neo-tree = { + enable = true; + + sources = ["filesystem" "buffers" "git_status"]; + + addBlankLineAtTop = false; + autoCleanAfterSessionRestore = false; + closeIfLastWindow = false; + closeFloatsOnEscapeKey = true; + defaultSource = "filesystem"; + enableDiagnostics = true; + enableGitStatus = true; + enableModifiedMarkers = true; + enableRefreshOnWrite = true; + gitStatusAsync = true; + gitStatusAsyncOptions = { + batchSize = 1000; + batchDelay = 10; + maxLines = 10000; + }; + hideRootNode = false; + retainHiddenRootIndent = false; + logLevel = "info"; + logToFile = false; + openFilesInLastWindow = true; + popupBorderStyle = "NC"; + resizeTimerInterval = 500; + sortCaseInsensitive = false; + sortFunction = "nil"; + usePopupsForInput = true; + useDefaultMappings = true; + sourceSelector = { + winbar = false; + statusline = false; + showScrolledOffParentNode = false; + tabLabels = { + filesystem = "  Files "; + buffers = "  Buffers "; + gitStatus = "  Git "; + diagnostics = " 裂Diagnostics "; + }; + contentLayout = "start"; + tabsLayout = "equal"; + truncationCharacter = "…"; + tabsMinWidth = null; + tabsMaxWidth = null; + padding = 0; + separator = { + left = "▏"; + right = "▕"; + }; + separatorActive = null; + showSeparatorOnEdge = false; + highlightTab = "NeoTreeTabInactive"; + highlightTabActive = "NeoTreeTabActive"; + highlightBackground = "NeoTreeTabInactive"; + highlightSeparator = "NeoTreeTabSeparatorInactive"; + highlightSeparatorActive = "NeoTreeTabSeparatorActive"; + }; + eventHandlers = { + before_render = '' + function (state) + -- add something to the state that can be used by custom components + end + ''; + + file_opened = '' + function(file_path) + --auto close + require("neo-tree").close_all() + end + ''; + }; + defaultComponentConfigs = { + container = { + enableCharacterFade = true; + width = "100%"; + rightPadding = 0; + }; + diagnostics = { + symbols = { + hint = "H"; + info = "I"; + warn = "!"; + error = "X"; + }; + highlights = { + hint = "DiagnosticSignHint"; + info = "DiagnosticSignInfo"; + warn = "DiagnosticSignWarn"; + error = "DiagnosticSignError"; + }; + }; + indent = { + indentSize = 2; + padding = 1; + withMarkers = true; + indentMarker = "│"; + lastIndentMarker = "└"; + highlight = "NeoTreeIndentMarker"; + withExpanders = null; + expanderCollapsed = ""; + expanderExpanded = ""; + expanderHighlight = "NeoTreeExpander"; + }; + icon = { + folderClosed = ""; + folderOpen = ""; + folderEmpty = "ﰊ"; + folderEmptyOpen = "ﰊ"; + default = "*"; + highlight = "NeoTreeFileIcon"; + }; + modified = { + symbol = "[+] "; + highlight = "NeoTreeModified"; + }; + name = { + trailingSlash = false; + useGitStatusColors = true; + highlight = "NeoTreeFileName"; + }; + gitStatus = { + symbols = { + added = "✚"; + deleted = "✖"; + modified = ""; + renamed = ""; + untracked = ""; + ignored = ""; + unstaged = ""; + staged = ""; + conflict = ""; + }; + + align = "right"; + }; + }; + renderers = { + directory = [ + "indent" + "icon" + "current_filter" + { + name = "container"; + content = [ + { + name = "name"; + zindex = 10; + } + { + name = "clipboard"; + zindex = 10; + } + { + name = "diagnostics"; + errors_only = true; + zindex = 20; + align = "right"; + hide_when_expanded = true; + } + { + name = "git_status"; + zindex = 20; + align = "right"; + hide_when_expanded = true; + } + ]; + } + ]; + file = [ + "indent" + "icon" + { + name = "container"; + content = [ + { + name = "name"; + zindex = 10; + } + { + name = "clipboard"; + zindex = 10; + } + { + name = "bufnr"; + zindex = 10; + } + { + name = "modified"; + zindex = 20; + align = "right"; + } + { + name = "diagnostics"; + zindex = 20; + align = "right"; + } + { + name = "git_status"; + zindex = 20; + align = "right"; + } + ]; + } + ]; + message = [ + { + name = "indent"; + with_markers = false; + } + { + name = "name"; + highlight = "NeoTreeMessage"; + } + ]; + terminal = [ + "indent" + "icon" + "name" + "bufnr" + ]; + }; + nestingRules = {}; + window = { + position = "left"; + width = 40; + height = 15; + autoExpandWidth = false; + popup = { + size = { + height = "80%"; + width = "50%"; + }; + position = "80%"; + }; + sameLevel = false; + insertAs = "child"; + mappingOptions = { + noremap = true; + nowait = true; + }; + mappings = { + "" = { + command = "toggle_node"; + # disable `nowait` if you have existing combos starting with this char that you want to use + nowait = false; + }; + "<2-LeftMouse>" = "open"; + "" = "open"; + "" = "revert_preview"; + P = { + command = "toggle_preview"; + config = {use_float = true;}; + }; + l = "focus_preview"; + S = "open_split"; + # S = "split_with_window_picker"; + s = "open_vsplit"; + # s = "vsplit_with_window_picker"; + t = "open_tabnew"; + # "" = "open_drop"; + # t = "open_tab_drop"; + w = "open_with_window_picker"; + C = "close_node"; + z = "close_all_nodes"; + # Z = "expand_all_nodes"; + R = "refresh"; + a = { + command = "add"; + # some commands may take optional config options, see `:h neo-tree-mappings` for details + config = { + show_path = "none"; # "none", "relative", "absolute" + }; + }; + A = "add_directory"; # also accepts the config.show_path and config.insert_as options. + d = "delete"; + r = "rename"; + y = "copy_to_clipboard"; + x = "cut_to_clipboard"; + p = "paste_from_clipboard"; + c = "copy"; # takes text input for destination, also accepts the config.show_path and config.insert_as options + m = "move"; # takes text input for destination, also accepts the config.show_path and config.insert_as options + e = "toggle_auto_expand_width"; + q = "close_window"; + "?" = "show_help"; + "<" = "prev_source"; + ">" = "next_source"; + }; + }; + filesystem = { + window = { + mappings = { + H = "toggle_hidden"; + "/" = "fuzzy_finder"; + D = "fuzzy_finder_directory"; + # "/" = "filter_as_you_type"; # this was the default until v1.28 + "#" = "fuzzy_sorter"; # fuzzy sorting using the fzy algorithm + # D = "fuzzy_sorter_directory"; + f = "filter_on_submit"; + "" = "clear_filter"; + "" = "navigate_up"; + "." = "set_root"; + "[g" = "prev_git_modified"; + "]g" = "next_git_modified"; + }; + }; + asyncDirectoryScan = "auto"; + scanMode = "shallow"; + bindToCwd = true; + cwdTarget = { + sidebar = "tab"; + current = "window"; + }; + filteredItems = { + visible = false; + forceVisibleInEmptyFolder = false; + showHiddenCount = true; + hideDotfiles = true; + hideGitignored = true; + hideHidden = true; + hideByName = [ + ".DS_Store" + "thumbs.db" + ]; + hideByPattern = []; + alwaysShow = []; + neverShow = []; + neverShowByPattern = []; + }; + findByFullPathWords = false; + findCommand = "fd"; + findArgs = { + fd = [ + "--exclude" + ".git" + "--exclude" + "node_modules" + ]; + }; + groupEmptyDirs = false; + searchLimit = 50; + followCurrentFile = false; + hijackNetrwBehavior = "open_default"; + useLibuvFileWatcher = false; + }; + buffers = { + bindToCwd = true; + followCurrentFile = true; + groupEmptyDirs = true; + window = { + mappings = { + "" = "navigate_up"; + "." = "set_root"; + bd = "buffer_delete"; + }; + }; + }; + gitStatus = { + window = { + mappings = { + A = "git_add_all"; + gu = "git_unstage_file"; + ga = "git_add_file"; + gr = "git_revert_file"; + gc = "git_commit"; + gp = "git_push"; + gg = "git_commit_and_push"; + }; + }; + }; + example = { + renderers = { + custom = [ + "indent" + { + name = "icon"; + default = "C"; + } + "custom" + "name" + ]; + }; + window = { + mappings = { + "" = "toggle_node"; + "" = "example_command"; + d = "show_debug_info"; + }; + }; + }; + }; + }; +}