diff --git a/plugins/default.nix b/plugins/default.nix index ffccda3c2e..c4ea2eb870 100644 --- a/plugins/default.nix +++ b/plugins/default.nix @@ -43,7 +43,7 @@ ./git/gitlinker.nix ./git/gitmessenger.nix ./git/gitsigns.nix - ./git/neogit.nix + ./git/neogit ./languages/clangd-extensions.nix ./languages/debugprint.nix diff --git a/plugins/git/neogit.nix b/plugins/git/neogit.nix deleted file mode 100644 index 5a35c624a3..0000000000 --- a/plugins/git/neogit.nix +++ /dev/null @@ -1,243 +0,0 @@ -{ - lib, - helpers, - config, - pkgs, - ... -}: -with lib; let - cfg = config.plugins.neogit; - - sectionDefaultsModule = types.submodule { - options = { - folded = mkOption { - type = types.nullOr types.bool; - default = null; - }; - }; - }; -in { - options = { - plugins.neogit = { - enable = mkEnableOption "neogit"; - - package = helpers.mkPackageOption "neogit" pkgs.vimPlugins.neogit; - - disableSigns = mkOption { - description = "Disable signs"; - type = types.nullOr types.bool; - default = null; - }; - - disableHint = mkOption { - description = "Disable hint"; - type = types.nullOr types.bool; - default = null; - }; - - disableContextHighlighting = mkOption { - description = "Disable the context highlighting"; - type = types.nullOr types.bool; - default = null; - }; - - disableCommitConfirmation = mkOption { - description = "Disable the commit confirmation prompt"; - type = types.nullOr types.bool; - default = null; - }; - - autoRefresh = mkOption { - description = "Enable Auto Refresh"; - type = types.nullOr types.bool; - default = null; - }; - - disableBuiltinNotifications = mkOption { - description = "Disable builtin notifications"; - type = types.nullOr types.bool; - default = null; - }; - - useMagitKeybindings = mkOption { - description = "Enable Magit keybindings"; - type = types.nullOr types.bool; - default = null; - }; - - graphStyle = helpers.defaultNullOpts.mkEnumFirstDefault ["ascii" "unicode"] '' - "ascii" is the graph the git CLI generates - "unicode" is the graph like https://github.com/rbong/vim-flog - ''; - - commitPopup = mkOption { - description = "Commit popup configuration"; - type = types.submodule { - options = { - kind = mkOption { - type = types.nullOr types.str; - default = null; - }; - }; - }; - default = {}; - }; - - kind = mkOption { - description = "The way of opening neogit"; - type = types.nullOr types.str; - default = null; - }; - - signs = mkOption { - description = "Customize displayed signs"; - type = types.submodule { - options = { - section = mkOption { - description = "Cosed and opened signs for sections"; - type = types.nullOr (types.listOf types.str); - default = null; - }; - - item = mkOption { - description = "Cosed and opened signs for items"; - type = types.nullOr (types.listOf types.str); - default = null; - }; - - hunk = mkOption { - description = "Cosed and opened signs for hunks"; - type = types.nullOr (types.listOf types.str); - default = null; - }; - }; - }; - default = {}; - }; - - integrations = mkOption { - description = "Tools integration"; - type = types.submodule { - options = { - diffview = mkOption { - description = "Enable diff popup"; - type = types.bool; - default = false; - }; - }; - }; - default = {}; - }; - - sections = mkOption { - description = "Section configuration"; - type = types.submodule { - options = { - untracked = mkOption { - description = "Options for untracked section"; - type = types.nullOr sectionDefaultsModule; - default = null; - }; - unstaged = mkOption { - description = "Options for unstaged section"; - type = types.nullOr sectionDefaultsModule; - default = null; - }; - staged = mkOption { - description = "Options for staged section"; - type = types.nullOr sectionDefaultsModule; - default = null; - }; - stashes = mkOption { - description = "Options for stashes section"; - type = types.nullOr sectionDefaultsModule; - default = null; - }; - unpulled = mkOption { - description = "Options for unpulled section"; - type = types.nullOr sectionDefaultsModule; - default = null; - }; - unmerged = mkOption { - description = "Options for unmerged section"; - type = types.nullOr sectionDefaultsModule; - default = null; - }; - recent = mkOption { - description = "Options for recent commits section"; - type = types.nullOr sectionDefaultsModule; - default = null; - }; - }; - }; - default = {}; - }; - - mappings = mkOption { - description = "Custom mappings"; - type = types.submodule { - options = { - status = mkOption { - type = types.nullOr (types.attrsOf (types.enum [ - "Close" - "Depth1" - "Depth2" - "Depth3" - "Depth4" - "Toggle" - "Discard" - "Stage" - "StageUnstaged" - "StageAll" - "GoToFile" - "Unstaged" - "UnstagedStage" - "CommandHistory" - "RefreshBuffer" - "HelpPopup" - "PullPopup" - "PushPopup" - "CommitPopup" - "LogPopup" - "StashPopup" - "BranchPopup" - ])); - default = null; - }; - }; - }; - default = {}; - }; - }; - }; - - config = let - setupOptions = with cfg; - helpers.toLuaObject { - inherit kind integrations signs sections mappings; - disable_signs = disableSigns; - disable_hint = disableHint; - disable_context_highlighting = disableContextHighlighting; - disable_commit_confirmation = disableCommitConfirmation; - auto_refresh = autoRefresh; - disable_builtin_notifications = disableBuiltinNotifications; - use_magit_keybindings = useMagitKeybindings; - commit_popup = commitPopup; - graph_style = graphStyle; - }; - in - mkIf cfg.enable { - extraPlugins = with pkgs.vimPlugins; - [ - cfg.package - plenary-nvim - ] - ++ optional cfg.integrations.diffview diffview-nvim; - - extraPackages = [pkgs.git]; - - extraConfigLua = '' - require('neogit').setup(${setupOptions}) - ''; - }; -} diff --git a/plugins/git/neogit/default.nix b/plugins/git/neogit/default.nix new file mode 100644 index 0000000000..a04cefa02e --- /dev/null +++ b/plugins/git/neogit/default.nix @@ -0,0 +1,104 @@ +{ + lib, + helpers, + config, + pkgs, + ... +}: +with lib; + helpers.neovim-plugin.mkNeovimPlugin config { + name = "neogit"; + defaultPackage = pkgs.vimPlugins.neogit; + extraPackages = [pkgs.git]; + + maintainers = [maintainers.GaetanLepage]; + + # TODO introduced 2024-02-29: remove 2024-04-29 + deprecateExtraOptions = true; + imports = + map ( + optionPath: + mkRemovedOptionModule + (["plugins" "neogit"] ++ optionPath) + "This option has been removed upstream. Please refer to the plugin documentation to update your configuration." + ) + [ + ["disableCommitConfirmation"] + ["disableBuiltinNotifications"] + ["useMagitKeybindings "] + ["commitPopup"] + ["sections" "unmerged"] + ["sections" "unpulled"] + ]; + optionsRenamedToSettings = [ + "disableSigns" + "disableHint" + "disableContextHighlighting" + "autoRefresh" + "graphStyle" + "kind" + "signs" + "integrations" + ["sections" "untracked"] + ["sections" "unstaged"] + ["sections" "staged"] + ["sections" "stashes"] + ["sections" "recent"] + "mappings" + ]; + + settingsOptions = import ./options.nix {inherit lib helpers;}; + + settingsExample = { + kind = "floating"; + commit_popup.kind = "floating"; + preview_buffer.kind = "floating"; + popup.kind = "floating"; + integrations.diffview = false; + disable_commit_confirmation = true; + disable_builtin_notifications = true; + sections = { + untracked.folded = false; + unstaged.folded = false; + staged.folded = false; + stashes.folded = false; + unpulled.folded = false; + unmerged.folded = true; + recent.folded = true; + }; + mappings = { + status = { + l = "Toggle"; + a = "Stage"; + }; + }; + }; + + extraConfig = cfg: { + assertions = + map + ( + name: { + assertion = let + enabled = cfg.settings.integrations.${name}; + isEnabled = (isBool enabled) && enabled; + in + isEnabled + -> config.plugins.${name}.enable; + message = '' + Nixvim (plugins.neogit): You have enabled the `${name}` integration, but `plugins.${name}.enable` is `false`. + ''; + } + ) + [ + "telescope" + "diffview" + "fzf-lua" + ]; + + extraPackages = + optional + (hasInfix "which" (cfg.settings.commit_view.verify_commit.__raw or "")) + pkgs.which; + }; + } diff --git a/plugins/git/neogit/options.nix b/plugins/git/neogit/options.nix new file mode 100644 index 0000000000..82043675fb --- /dev/null +++ b/plugins/git/neogit/options.nix @@ -0,0 +1,427 @@ +{ + lib, + helpers, +}: +with lib; let + mkKindOption = + helpers.defaultNullOpts.mkEnum + [ + "split" + "vsplit" + "split_above" + "tab" + "floating" + "replace" + "auto" + ]; +in { + filewatcher = { + interval = helpers.defaultNullOpts.mkUnsignedInt 1000 '' + Interval between two refreshes. + ''; + + enabled = helpers.defaultNullOpts.mkBool true '' + When enabled, will watch the `.git/` directory for changes and refresh the status buffer + in response to filesystem events. + ''; + }; + + graph_style = helpers.defaultNullOpts.mkEnumFirstDefault ["ascii" "unicode"] '' + - "ascii" is the graph the git CLI generates + - "unicode" is the graph like https://github.com/rbong/vim-flog + ''; + + disable_hint = helpers.defaultNullOpts.mkBool false '' + Hides the hints at the top of the status buffer. + ''; + + disable_context_highlighting = helpers.defaultNullOpts.mkBool false '' + Disables changing the buffer highlights based on where the cursor is. + ''; + + disable_signs = helpers.defaultNullOpts.mkBool false '' + Disables signs for sections/items/hunks. + ''; + + git_services = + helpers.defaultNullOpts.mkAttrsOf types.str + '' + { + "github.com" = "https://github.com/$\{owner}/$\{repository}/compare/$\{branch_name}?expand=1"; + "bitbucket.org" = "https://bitbucket.org/$\{owner}/$\{repository}/pull-requests/new?source=$\{branch_name}&t=1"; + "gitlab.com" = "https://gitlab.com/$\{owner}/$\{repository}/merge_requests/new?merge_request[source_branch]=$\{branch_name}"; + } + '' + "Used to generate URL's for branch popup action 'pull request'."; + + fetch_after_checkout = helpers.defaultNullOpts.mkBool false '' + Perform a fetch if the newly checked out branch has an upstream or pushRemote set. + ''; + + telescope_sorter = helpers.mkNullOrLuaFn '' + Allows a different telescope sorter. + Defaults to 'fuzzy_with_index_bias'. + The example below will use the native fzf sorter instead. + By default, this function returns `nil`. + + Example: + ```lua + require("telescope").extensions.fzf.native_fzf_sorter + ``` + ''; + + disable_insert_on_commit = + helpers.defaultNullOpts.mkNullable + (with types; either bool (enum ["auto"])) + "auto" + '' + Changes what mode the Commit Editor starts in. + `true` will leave nvim in normal mode, `false` will change nvim to insert mode, and `"auto"` + will change nvim to insert mode IF the commit message is empty, otherwise leaving it in normal + mode. + ''; + + use_per_project_settings = helpers.defaultNullOpts.mkBool true '' + Scope persisted settings on a per-project basis. + ''; + + remember_settings = helpers.defaultNullOpts.mkBool true '' + Persist the values of switches/options within and across sessions. + ''; + + auto_refresh = helpers.defaultNullOpts.mkBool true '' + Neogit refreshes its internal state after specific events, which can be expensive depending on + the repository size. + Disabling `auto_refresh` will make it so you have to manually refresh the status after you open + it. + ''; + + sort_branches = helpers.defaultNullOpts.mkStr "-committerdate" '' + Value used for `--sort` option for `git branch` command. + By default, branches will be sorted by commit date descending. + Flag description: https://git-scm.com/docs/git-branch#Documentation/git-branch.txt---sortltkeygt + Sorting keys: https://git-scm.com/docs/git-for-each-ref#_options + ''; + + kind = mkKindOption "tab" '' + Change the default way of opening neogit. + ''; + + disable_line_numbers = helpers.defaultNullOpts.mkBool true '' + Disable line numbers and relative line numbers. + ''; + + console_timeout = helpers.defaultNullOpts.mkUnsignedInt 2000 '' + The time after which an output console is shown for slow running commands. + ''; + + auto_show_console = helpers.defaultNullOpts.mkBool true '' + Automatically show console if a command takes more than `consoleTimeout` milliseconds. + ''; + + status = { + recent_commit_count = helpers.defaultNullOpts.mkUnsignedInt 10 '' + Recent commit count in the status buffer. + ''; + }; + + commit_editor = { + kind = mkKindOption "auto" "The type of window that should be opened."; + }; + + commit_select_view = { + kind = mkKindOption "tab" "The type of window that should be opened."; + }; + + commit_view = { + kind = mkKindOption "vsplit" "The type of window that should be opened."; + + verify_commit = helpers.mkNullOrStrLuaOr types.bool '' + Show commit signature information in the buffer. + Can be set to true or false, otherwise we try to find the binary. + + Default: "os.execute('which gpg') == 0" + ''; + }; + + log_view = { + kind = mkKindOption "tab" "The type of window that should be opened."; + }; + + rebase_editor = { + kind = mkKindOption "auto" "The type of window that should be opened."; + }; + + reflog_view = { + kind = mkKindOption "tab" "The type of window that should be opened."; + }; + + merge_editor = { + kind = mkKindOption "auto" "The type of window that should be opened."; + }; + + description_editor = { + kind = mkKindOption "auto" "The type of window that should be opened."; + }; + + tag_editor = { + kind = mkKindOption "auto" "The type of window that should be opened."; + }; + + preview_buffer = { + kind = mkKindOption "split" "The type of window that should be opened."; + }; + + popup = { + kind = mkKindOption "split" "The type of window that should be opened."; + }; + + signs = + mapAttrs + (n: v: + helpers.defaultNullOpts.mkListOf types.str ''["${v.closed}" "${v.opened}"]'' '' + The icons to use for open and closed ${n}s. + '') + { + hunk = { + closed = ""; + opened = ""; + }; + item = { + closed = ">"; + opened = "v"; + }; + section = { + closed = ">"; + opened = "v"; + }; + }; + + integrations = { + telescope = helpers.mkNullOrOption types.bool '' + If enabled, use telescope for menu selection rather than `vim.ui.select`. + Allows multi-select and some things that `vim.ui.select` doesn't. + ''; + + diffview = helpers.mkNullOrOption types.bool '' + Neogit only provides inline diffs. + If you want a more traditional way to look at diffs, you can use `diffview`. + The diffview integration enables the diff popup. + ''; + + fzf-lua = helpers.mkNullOrOption types.bool '' + If enabled, uses fzf-lua for menu selection. + If the telescope integration is also selected then telescope is used instead. + ''; + }; + + sections = + mapAttrs + ( + name: default: + mkOption { + type = with types; + nullOr (submodule { + options = { + folded = mkOption { + type = types.bool; + description = "Whether or not this section should be open or closed by default."; + }; + + hidden = mkOption { + type = types.bool; + description = "Whether or not this section should be shown."; + }; + }; + }); + inherit default; + description = "Settings for the ${name} section"; + } + ) + { + sequencer = { + folded = false; + hidden = false; + }; + untracked = { + folded = false; + hidden = false; + }; + unstaged = { + folded = false; + hidden = false; + }; + staged = { + folded = false; + hidden = false; + }; + stashes = { + folded = true; + hidden = false; + }; + unpulled_upstream = { + folded = true; + hidden = false; + }; + unmerged_upstream = { + folded = false; + hidden = false; + }; + unpulled_pushRemote = { + folded = true; + hidden = false; + }; + unmerged_pushRemote = { + folded = false; + hidden = false; + }; + recent = { + folded = true; + hidden = false; + }; + rebase = { + folded = true; + hidden = false; + }; + }; + + ignored_settings = + helpers.defaultNullOpts.mkListOf types.str + '' + [ + "NeogitPushPopup--force-with-lease" + "NeogitPushPopup--force" + "NeogitPullPopup--rebase" + "NeogitCommitPopup--allow-empty" + "NeogitRevertPopup--no-edit" + ] + '' + '' + Table of settings to never persist. + Uses format "Filetype--cli-value". + ''; + + mappings = let + mkMappingOption = helpers.defaultNullOpts.mkAttrsOf (with types; either str (enum [false])); + in { + commit_editor = + mkMappingOption + '' + { + q = "Close"; + "" = "Submit"; + "" = "Abort"; + } + '' + "Mappings for the commit editor."; + + rebase_editor = + mkMappingOption + '' + { + p = "Pick"; + r = "Reword"; + e = "Edit"; + s = "Squash"; + f = "Fixup"; + x = "Execute"; + d = "Drop"; + b = "Break"; + q = "Close"; + "" = "OpenCommit"; + gk = "MoveUp"; + gj = "MoveDown"; + "" = "Submit"; + "" = "Abort"; + } + '' + "Mappings for the rebase editor."; + + finder = + mkMappingOption + '' + { + "" = "Select"; + "" = "Close"; + "" = "Close"; + "" = "Next"; + "" = "Previous"; + "" = "Next"; + "" = "Previous"; + "" = "MultiselectToggleNext"; + "" = "MultiselectTogglePrevious"; + "" = "NOP"; + } + '' + "Mappings for the finder."; + + popup = + mkMappingOption + '' + { + "?" = "HelpPopup"; + A = "CherryPickPopup"; + D = "DiffPopup"; + M = "RemotePopup"; + P = "PushPopup"; + X = "ResetPopup"; + Z = "StashPopup"; + b = "BranchPopup"; + c = "CommitPopup"; + f = "FetchPopup"; + l = "LogPopup"; + m = "MergePopup"; + p = "PullPopup"; + r = "RebasePopup"; + v = "RevertPopup"; + } + '' + "Mappings for popups."; + + status = + mkMappingOption + '' + { + q = "Close"; + I = "InitRepo"; + "1" = "Depth1"; + "2" = "Depth2"; + "3" = "Depth3"; + "4" = "Depth4"; + "" = "Toggle"; + x = "Discard"; + s = "Stage"; + S = "StageUnstaged"; + "" = "StageAll"; + u = "Unstage"; + U = "UnstageStaged"; + "$" = "CommandHistory"; + "#" = "Console"; + Y = "YankSelected"; + "" = "RefreshBuffer"; + "" = "GoToFile"; + "" = "VSplitOpen"; + "" = "SplitOpen"; + "" = "TabOpen"; + "{" = "GoToPreviousHunkHeader"; + "}" = "GoToNextHunkHeader"; + } + '' + "Mappings for status."; + }; + + notification_icon = helpers.defaultNullOpts.mkStr "󰊢" '' + Icon for notifications. + ''; + + use_default_keymaps = helpers.defaultNullOpts.mkBool true '' + Set to false if you want to be responsible for creating _ALL_ keymappings. + ''; + + highlight = + genAttrs ["italic" "bold" "underline"] + ( + n: + helpers.defaultNullOpts.mkBool true "Set the ${n} property of the highlight group." + ); +} diff --git a/tests/test-sources/plugins/git/neogit.nix b/tests/test-sources/plugins/git/neogit.nix new file mode 100644 index 0000000000..881554a773 --- /dev/null +++ b/tests/test-sources/plugins/git/neogit.nix @@ -0,0 +1,258 @@ +{pkgs, ...}: { + empty = { + plugins.neogit.enable = true; + }; + + defaults = { + # Otherwise `os.execute('which gpg')` returns an error and make the whole test derivation fail + # (option `settings.commit_view.verify_commit`) + extraPackages = [pkgs.gnupg]; + + plugins.neogit = { + enable = true; + + settings = { + filewatcher = { + interval = 1000; + enabled = true; + }; + graph_style = "ascii"; + disable_hint = false; + disable_context_highlighting = false; + disable_signs = false; + git_services = { + "github.com" = "https://github.com/$\{owner}/$\{repository}/compare/$\{branch_name}?expand=1"; + "bitbucket.org" = "https://bitbucket.org/$\{owner}/$\{repository}/pull-requests/new?source=$\{branch_name}&t=1"; + "gitlab.com" = "https://gitlab.com/$\{owner}/$\{repository}/merge_requests/new?merge_request[source_branch]=$\{branch_name}"; + }; + telescope_sorter = null; + disable_insert_on_commit = "auto"; + use_per_project_settings = true; + remember_settings = true; + auto_refresh = true; + sort_branches = "-committerdate"; + kind = "tab"; + disable_line_numbers = true; + console_timeout = 2000; + auto_show_console = true; + status = { + recent_commit_count = 10; + }; + commit_editor = { + kind = "auto"; + }; + commit_select_view = { + kind = "tab"; + }; + commit_view = { + kind = "vsplit"; + verify_commit = "os.execute('which gpg') == 0"; + }; + log_view = { + kind = "tab"; + }; + rebase_editor = { + kind = "auto"; + }; + reflog_view = { + kind = "tab"; + }; + merge_editor = { + kind = "auto"; + }; + description_editor = { + kind = "auto"; + }; + tag_editor = { + kind = "auto"; + }; + preview_buffer = { + kind = "split"; + }; + popup = { + kind = "split"; + }; + signs = { + hunk = ["" ""]; + item = [">" "v"]; + section = [">" "v"]; + }; + integrations = { + telescope = null; + diffview = null; + fzf-lua = null; + }; + sections = { + sequencer = { + folded = false; + hidden = false; + }; + untracked = { + folded = false; + hidden = false; + }; + unstaged = { + folded = false; + hidden = false; + }; + staged = { + folded = false; + hidden = false; + }; + stashes = { + folded = true; + hidden = false; + }; + unpulled_upstream = { + folded = true; + hidden = false; + }; + unmerged_upstream = { + folded = false; + hidden = false; + }; + unpulled_pushRemote = { + folded = true; + hidden = false; + }; + unmerged_pushRemote = { + folded = false; + hidden = false; + }; + recent = { + folded = true; + hidden = false; + }; + rebase = { + folded = true; + hidden = false; + }; + }; + ignored_settings = [ + "NeogitPushPopup--force-with-lease" + "NeogitPushPopup--force" + "NeogitPullPopup--rebase" + "NeogitCommitPopup--allow-empty" + "NeogitRevertPopup--no-edit" + ]; + + mappings = { + commit_editor = { + q = "Close"; + "" = "Submit"; + "" = "Abort"; + }; + rebase_editor = { + p = "Pick"; + r = "Reword"; + e = "Edit"; + s = "Squash"; + f = "Fixup"; + x = "Execute"; + d = "Drop"; + b = "Break"; + q = "Close"; + "" = "OpenCommit"; + gk = "MoveUp"; + gj = "MoveDown"; + "" = "Submit"; + "" = "Abort"; + }; + finder = { + "" = "Select"; + "" = "Close"; + "" = "Close"; + "" = "Next"; + "" = "Previous"; + "" = "Next"; + "" = "Previous"; + "" = "MultiselectToggleNext"; + "" = "MultiselectTogglePrevious"; + "" = "NOP"; + }; + popup = { + "?" = "HelpPopup"; + A = "CherryPickPopup"; + D = "DiffPopup"; + M = "RemotePopup"; + P = "PushPopup"; + X = "ResetPopup"; + Z = "StashPopup"; + b = "BranchPopup"; + c = "CommitPopup"; + f = "FetchPopup"; + l = "LogPopup"; + m = "MergePopup"; + p = "PullPopup"; + r = "RebasePopup"; + v = "RevertPopup"; + }; + status = { + q = "Close"; + I = "InitRepo"; + "1" = "Depth1"; + "2" = "Depth2"; + "3" = "Depth3"; + "4" = "Depth4"; + "" = "Toggle"; + x = "Discard"; + s = "Stage"; + S = "StageUnstaged"; + "" = "StageAll"; + u = "Unstage"; + U = "UnstageStaged"; + "$" = "CommandHistory"; + "#" = "Console"; + Y = "YankSelected"; + "" = "RefreshBuffer"; + "" = "GoToFile"; + "" = "VSplitOpen"; + "" = "SplitOpen"; + "" = "TabOpen"; + "{" = "GoToPreviousHunkHeader"; + "}" = "GoToNextHunkHeader"; + }; + }; + notification_icon = "󰊢"; + use_default_keymaps = true; + highlight = { + italic = true; + bold = true; + underline = true; + }; + }; + }; + }; + + example = { + plugins.neogit = { + enable = true; + + settings = { + kind = "floating"; + commit_popup.kind = "floating"; + preview_buffer.kind = "floating"; + popup.kind = "floating"; + integrations.diffview = false; + disable_commit_confirmation = true; + disable_builtin_notifications = true; + sections = { + untracked = { + folded = true; + hidden = true; + }; + unmerged = { + folded = true; + hidden = false; + }; + }; + mappings = { + status = { + l = "Toggle"; + a = "Stage"; + }; + }; + }; + }; + }; +}