plugins/lspsaga: adapt to underlying plugin now being nvimdev/lspsaga.nvim

This commit is contained in:
Gaetan Lepage 2023-08-10 18:35:32 +02:00 committed by Gaétan Lepage
parent 1f0d85640b
commit 3d96207cb7
2 changed files with 763 additions and 185 deletions

View file

@ -7,220 +7,634 @@
with lib; let
cfg = config.plugins.lspsaga;
helpers = import ../helpers.nix {inherit lib;};
mkKeymapOption = default:
helpers.defaultNullOpts.mkNullable
(with types; either str (listOf str))
(
if isString default
then default
else "[${concatStringsSep " " default}]"
);
in {
imports = let
basePluginPath = ["plugins" "lspsaga"];
in
map
(
optionName:
# See https://github.com/NixOS/nixpkgs/pull/247916
mkRemovedOptionModule
(basePluginPath ++ [optionName])
''
The `lspsaga` plugin used by default in Nixvim has changed to https://github.com/nvimdev/lspsaga.nvim.
Please, adapt your config to the new options.
Documentation available at https://nix-community.github.io/nixvim/plugins/lspsaga/index.html.
''
)
[
"signs"
"headers"
"maxDialogWidth"
"icons"
"maxFinderPreviewLines"
"keys"
"borderStyle"
"renamePromptPrefix"
];
options = {
plugins.lspsaga = {
enable = mkEnableOption "lspsaga.nvim";
plugins.lspsaga =
helpers.extraOptionsOptions
// {
enable = mkEnableOption "lspsaga.nvim";
package = helpers.mkPackageOption "lspsaga" pkgs.vimPlugins.lspsaga-nvim;
package = helpers.mkPackageOption "lspsaga" pkgs.vimPlugins.lspsaga-nvim;
signs = {
use = mkOption {
default = true;
type = types.bool;
description = "Whether to use diagnostic signs";
ui = {
border =
helpers.defaultNullOpts.mkBorder
"single"
"lspsaga"
"";
devicon = helpers.defaultNullOpts.mkBool true "Whether to use nvim-web-devicons.";
title = helpers.defaultNullOpts.mkBool true "Show title in some float window.";
expand = helpers.defaultNullOpts.mkStr "" "Expand icon.";
collapse = helpers.defaultNullOpts.mkStr "" "Collapse icon.";
codeAction = helpers.defaultNullOpts.mkStr "💡" "Code action icon.";
actionfix = helpers.defaultNullOpts.mkStr "" "Action fix icon.";
lines =
helpers.defaultNullOpts.mkNullable
(with types; listOf str)
''["" "" "" "" ""]''
"Symbols used in virtual text connect.";
kind = helpers.defaultNullOpts.mkNullable types.attrs "{}" "LSP kind custom table.";
impSign = helpers.defaultNullOpts.mkStr "󰳛 " "Implement icon.";
};
error = mkOption {
type = types.nullOr types.str;
default = null;
description = "Error diagnostic sign";
hover = {
maxWidth = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.9" ''
Defines float window width.
'';
maxHeight = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.8" ''
Defines float window height.
'';
openLink = helpers.defaultNullOpts.mkStr "gx" "Key for opening links.";
openCmd = helpers.defaultNullOpts.mkStr "!chrome" "cmd for opening links.";
};
warning = mkOption {
type = types.nullOr types.str;
default = null;
description = "Warning diagnostic sign";
};
diagnostic = {
showCodeAction = helpers.defaultNullOpts.mkBool true ''
Show code action in diagnostic jump window.
Its useful. Suggested to set to `true`.
'';
hint = mkOption {
type = types.nullOr types.str;
default = null;
description = "Hint diagnostic sign";
};
showLayout = helpers.defaultNullOpts.mkStr "float" ''
Config layout of diagnostic window not jump window.
'';
info = mkOption {
type = types.nullOr types.str;
default = null;
description = "Info diagnostic sign";
};
};
showNormalHeight = helpers.defaultNullOpts.mkInt 10 ''
Show window height when diagnostic show window layout is normal.
'';
headers = {
error = mkOption {
type = types.nullOr types.str;
default = null;
description = "Error diagnostic header";
};
jumpNumShortcut = helpers.defaultNullOpts.mkBool true ''
Enable number shortcuts to execute code action quickly.
'';
warning = mkOption {
type = types.nullOr types.str;
default = null;
description = "Warning diagnostic header";
};
maxWidth = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.8" ''
Diagnostic jump window max width.
'';
hint = mkOption {
type = types.nullOr types.str;
default = null;
description = "Hint diagnostic header";
};
maxHeight = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.6" ''
Diagnostic jump window max height.
'';
info = mkOption {
type = types.nullOr types.str;
default = " Info";
description = "Info diagnostic header";
};
};
maxShowWidth = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.9" ''
Show window max width when layout is float.
'';
maxDialogWidth = mkOption {
type = types.nullOr types.int;
default = null;
description = "Maximum dialog width";
};
maxShowHeight = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.6" ''
Show window max height when layout is float.
'';
icons = {
codeAction = mkOption {
type = types.nullOr types.str;
default = null;
description = "Code action icon";
};
textHlFollow = helpers.defaultNullOpts.mkBool true ''
Diagnostic jump window text highlight follow diagnostic type.
'';
findDefinition = mkOption {
type = types.nullOr types.str;
default = null;
description = "Find definition icon";
};
borderFollow = helpers.defaultNullOpts.mkBool true ''
Diagnostic jump window border follow diagnostic type.
'';
findReference = mkOption {
type = types.nullOr types.str;
default = null;
description = "Find reference icon";
};
extendRelatedInformation = helpers.defaultNullOpts.mkBool false ''
When have `relatedInformation`, diagnostic message is extended to show it.
'';
definitionPreview = mkOption {
type = types.nullOr types.str;
default = null;
description = "Definition preview icon";
};
};
diagnosticOnlyCurrent = helpers.defaultNullOpts.mkBool false ''
Only show diagnostic virtual text on the current line.
'';
maxFinderPreviewLines = mkOption {
type = types.nullOr types.int;
default = null;
description = "Maximum finder preview lines";
};
keys = {
execAction = mkKeymapOption "o" "Execute action (in jump window).";
keys = let
defaultKeyOpt = desc:
mkOption {
description = desc;
type = types.nullOr types.str;
default = null;
quit = mkKeymapOption "q" "Quit key for the jump window.";
toggleOrJump = mkKeymapOption "<CR>" ''
Toggle or jump to position when in `diagnostic_show` window.
'';
quitInShow = mkKeymapOption ["q" "<ESC>"] ''
Quit key for the diagnostic_show window.
'';
};
in {
finderAction = {
open = defaultKeyOpt "Open from finder";
vsplit = defaultKeyOpt "Vertical split in finder";
split = defaultKeyOpt "Horizontal split in finder";
quit = defaultKeyOpt "Quit finder";
scrollDown = defaultKeyOpt "Scroll down finder";
scrollUp = defaultKeyOpt "Scroll up finder";
};
codeAction = {
quit = defaultKeyOpt "Quit code actions menu";
exec = defaultKeyOpt "Execute code action";
numShortcut = helpers.defaultNullOpts.mkBool true ''
Whether number shortcut for code actions are enabled.
'';
showServerName = helpers.defaultNullOpts.mkBool false ''
Show language server name.
'';
extendGitSigns = helpers.defaultNullOpts.mkBool false ''
Extend gitsigns plugin diff action.
'';
onlyInCursor = helpers.defaultNullOpts.mkBool true "";
keys = {
quit = mkKeymapOption "q" "Quit the float window.";
exec = mkKeymapOption "<CR>" "Execute action.";
};
};
lightbulb = {
enable = helpers.defaultNullOpts.mkBool true "Enable lightbulb.";
sign = helpers.defaultNullOpts.mkBool true "Show sign in status column.";
debounce = helpers.defaultNullOpts.mkInt 10 "Timer debounce.";
signPriority = helpers.defaultNullOpts.mkInt 40 "Sign priority.";
virtualText = helpers.defaultNullOpts.mkBool true "Show virtual text at the end of line.";
};
scrollPreview = {
scrollDown = mkKeymapOption "<C-f>" "Scroll down.";
scrollUp = mkKeymapOption "<C-b>" "Scroll up.";
};
finder = {
maxHeight = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.5" ''
`max_height` of the finder window (float layout).
'';
leftWidth = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.3" ''
Width of the left finder window (float layout).
'';
rightWidth = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.3" ''
Width of the right finder window (float layout).
'';
methods = helpers.defaultNullOpts.mkNullable (with types; attrsOf str) "{}" ''
Keys are alias of LSP methods.
Values are LSP methods, which you want show in finder.
Example:
```nix
{
tyd = "textDocument/typeDefinition";
}
```
'';
default = helpers.defaultNullOpts.mkStr "ref+imp" ''
Default search results shown, `ref` for "references" and `imp` for "implementation".
'';
layout = helpers.defaultNullOpts.mkEnumFirstDefault ["float" "normal"] ''
`normal` will use the normal layout window priority is lower than command layout.
'';
silent = helpers.defaultNullOpts.mkBool false ''
If its true, it will disable show the no response message.
'';
filter = helpers.defaultNullOpts.mkNullable (with types; attrsOf str) "{}" ''
Keys are LSP methods.
Values are a filter handler.
Function parameter are `client_id` and `result`.
'';
keys = {
shuttle = mkKeymapOption "[w" "Shuttle bettween the finder layout window.";
toggleOrOpen = mkKeymapOption "o" "Toggle expand or open.";
vsplit = mkKeymapOption "s" "Open in vsplit.";
split = mkKeymapOption "i" "Open in split.";
tabe = mkKeymapOption "t" "Open in tabe.";
tabnew = mkKeymapOption "r" "Open in new tab.";
quit = mkKeymapOption "q" "Quit the finder, only works in layout left window.";
close = mkKeymapOption "<C-c>k" "Close finder.";
};
};
definition = {
width = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.6" ''
Defines float window width.
'';
height = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.5" ''
Defines float window height.
'';
keys = {
edit = mkKeymapOption "<C-c>o" "edit";
vsplit = mkKeymapOption "<C-c>v" "vsplit";
split = mkKeymapOption "<C-c>i" "split";
tabe = mkKeymapOption "<C-c>t" "tabe";
quit = mkKeymapOption "q" "quit";
close = mkKeymapOption "<C-c>k" "close";
};
};
rename = {
inSelect = helpers.defaultNullOpts.mkBool true ''
Default is `true`.
Whether the name is selected when the float opens.
In some situation, just like want to change one or less characters, `inSelect` is not so
useful.
You can tell the Lspsaga to start in normal mode using an extra argument like
`:Lspsaga lsp_rename mode=n`.
'';
autoSave = helpers.defaultNullOpts.mkBool false ''
Auto save file when the rename is done.
'';
projectMaxWidth =
helpers.defaultNullOpts.mkNullable
(types.numbers.between 0.0 1.0)
"0.5"
"Width for the `project_replace` float window.";
projectMaxHeight =
helpers.defaultNullOpts.mkNullable
(types.numbers.between 0.0 1.0)
"0.5"
"Height for the `project_replace` float window.";
keys = {
quit = mkKeymapOption "<C-k>" "Quit rename window or `project_replace` window.";
exec = mkKeymapOption "<CR>" ''
Execute rename in `rename` window or execute replace in `project_replace` window.
'';
select = mkKeymapOption "x" ''
Select or cancel select item in `project_replace` float window.
'';
};
};
symbolInWinbar = {
enable = helpers.defaultNullOpts.mkBool true "Enable.";
separator = helpers.defaultNullOpts.mkStr " " "Separator character.";
hideKeyword = helpers.defaultNullOpts.mkBool false "Hide keyword.";
showFile = helpers.defaultNullOpts.mkBool true "Show file.";
folderLevel = helpers.defaultNullOpts.mkInt 1 "Folder level.";
colorMode = helpers.defaultNullOpts.mkBool true "Color mode.";
delay = helpers.defaultNullOpts.mkInt 300 "Delay.";
};
outline = {
winPosition = helpers.defaultNullOpts.mkStr "right" "`right` window position.";
winWidth = helpers.defaultNullOpts.mkInt 30 "Window width.";
autoPreview = helpers.defaultNullOpts.mkBool true ''
Auto preview when cursor moved in outline window.
'';
detail = helpers.defaultNullOpts.mkBool true "Show detail.";
autoClose = helpers.defaultNullOpts.mkBool true ''
Auto close itself when outline window is last window.
'';
closeAfterJump = helpers.defaultNullOpts.mkBool false "Close after jump.";
layout = helpers.defaultNullOpts.mkEnumFirstDefault ["normal" "float"] ''
`float` or `normal`.
Default is normal.
If set to float, above options will ignored.
'';
maxHeight = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.5" ''
Height of outline float layout.
'';
leftWidth = helpers.defaultNullOpts.mkNullable (types.numbers.between 0.0 1.0) "0.3" ''
Width of outline float layout left window.
'';
keys = {
toggleOrJump = mkKeymapOption "o" "Toggle or jump.";
quit = mkKeymapOption "q" "Quit outline window.";
jump = mkKeymapOption "e" "Jump to pos even on a expand/collapse node.";
};
};
callhierarchy = {
layout = helpers.defaultNullOpts.mkEnumFirstDefault ["float" "normal"] ''
- Layout `normal` and `float`.
- Or you can pass in an extra argument like `:Lspsaga incoming_calls ++normal`, which
overrides this option.
'';
keys = {
edit = mkKeymapOption "e" "Edit (open) file.";
vsplit = mkKeymapOption "s" "vsplit";
split = mkKeymapOption "i" "split";
tabe = mkKeymapOption "t" "Open in new tab.";
close = mkKeymapOption "<C-c>k" "Close layout.";
quit = mkKeymapOption "q" "Quit layout.";
shuttle = mkKeymapOption "[w" "Shuttle bettween the layout left and right.";
toggleOrReq = mkKeymapOption "u" "Toggle or do request.";
};
};
implement = {
enable = helpers.defaultNullOpts.mkBool true ''
When buffer has instances of the interface type, Lspsaga will show extra information for
it.
'';
sign = helpers.defaultNullOpts.mkBool true "Show sign in status column.";
virtualText = helpers.defaultNullOpts.mkBool true "Show virtual text at the end of line.";
priority = helpers.defaultNullOpts.mkInt 100 "Sign priority.";
};
beacon = {
enable = helpers.defaultNullOpts.mkBool true ''
In Lspsaga, some commands jump around in buffer(s).
With beacon enabled, it will show a beacon to tell you where the cursor is.
'';
frequency = helpers.defaultNullOpts.mkInt 7 "Frequency.";
};
};
borderStyle = mkOption {
type = types.nullOr (types.enum ["thin" "rounded" "thick"]);
default = null;
description = "Border style";
};
renamePromptPrefix = mkOption {
type = types.nullOr types.str;
default = null;
description = "Rename prompt prefix";
};
};
};
config = let
notDefault = default: opt:
if (opt != default)
then opt
else null;
notEmpty = opt:
if ((filterAttrs (_: v: v != null) opt) != {})
then opt
else null;
notNull = opt: opt;
lspsagaConfig = {
use_saga_diagnostic_sign = notDefault true cfg.signs.use;
error_sign = notNull cfg.signs.error;
warn_sign = notNull cfg.signs.warning;
hint_sign = notNull cfg.signs.hint;
infor_sign = notNull cfg.signs.info;
config = mkIf cfg.enable {
extraPlugins =
[cfg.package]
++ (
optional
(cfg.ui.devicon == null || cfg.ui.devicon)
pkgs.vimPlugins.nvim-web-devicons
);
# TODO Fix this!
# error_header = notNull cfg.headers.error;
# warn_header = notNull cfg.headers.warning;
# hint_header = notNull cfg.headers.hint;
# infor_header = notNull cfg.headers.info;
warnings =
mkIf (
# https://nvimdev.github.io/lspsaga/implement/#default-options
(isBool cfg.implement.enable && cfg.implement.enable)
&& (isBool cfg.symbolInWinbar.enable && !cfg.symbolInWinbar.enable)
)
["You have enabled the `implement` module but it requires `symbolInWinbar` to be enabled."];
max_diag_msg_width = notNull cfg.maxDialogWidth;
code_action_icon = notNull cfg.icons.codeAction;
finder_definition_icon = notNull cfg.icons.findDefinition;
finder_reference_icon = notNull cfg.icons.findReference;
definition_preview_icon = notNull cfg.icons.definitionPreview;
max_finder_preview_lines = notNull cfg.maxFinderPreviewLines;
rename_prompt_prefix = notNull cfg.renamePromptPrefix;
border_style = let
borderStyle =
if cfg.borderStyle == "thin"
then 1
else if cfg.borderStyle == "rounded"
then 2
else if cfg.borderStyle == "thick"
then 3
else null;
in
borderStyle;
finder_action_keys = let
keys = {
open = notNull cfg.keys.finderAction.open;
vsplit = notNull cfg.keys.finderAction.vsplit;
split = notNull cfg.keys.finderAction.split;
quit = notNull cfg.keys.finderAction.quit;
scroll_down = notNull cfg.keys.finderAction.scrollDown;
scroll_up = notNull cfg.keys.finderAction.scrollUp;
};
in
notEmpty keys;
code_action_keys = let
keys = {
quit = notNull cfg.keys.codeAction.quit;
exec = notNull cfg.keys.codeAction.exec;
};
in
notEmpty keys;
};
in
mkIf cfg.enable {
extraPlugins = [cfg.package];
extraConfigLua = ''
local saga = require 'lspsaga'
saga.init_lsp_saga(${helpers.toLuaObject lspsagaConfig})
'';
};
extraConfigLua = let
setupOptions = with cfg;
{
ui = with ui; {
inherit
border
devicon
title
expand
collapse
;
code_action = codeAction;
inherit
actionfix
lines
kind
;
imp_sign = impSign;
};
hover = with hover; {
max_width = maxWidth;
max_height = maxHeight;
open_link = openLink;
open_cmd = openCmd;
};
diagnostic = with diagnostic; {
show_code_action = showCodeAction;
show_layout = showLayout;
show_normal_height = showNormalHeight;
jump_num_shortcut = jumpNumShortcut;
max_width = maxWidth;
max_height = maxHeight;
max_show_width = maxShowWidth;
max_show_height = maxShowHeight;
text_hl_follow = textHlFollow;
border_follow = borderFollow;
extend_relatedInformation = extendRelatedInformation;
diagnostic_only_current = diagnosticOnlyCurrent;
keys = with keys; {
exec_action = execAction;
inherit quit;
toggle_or_jump = toggleOrJump;
quit_in_show = quitInShow;
};
};
code_action = with codeAction; {
num_shortcut = numShortcut;
show_server_name = showServerName;
extend_gitsigns = extendGitSigns;
only_in_cursor = onlyInCursor;
keys = with keys; {
inherit quit exec;
};
};
lightbulb = with lightbulb; {
inherit
enable
sign
debounce
;
sign_priority = signPriority;
virtual_text = virtualText;
};
scroll_preview = with scrollPreview; {
scroll_down = scrollDown;
scroll_up = scrollUp;
};
finder = with finder; {
max_height = maxHeight;
left_width = leftWidth;
right_width = rightWidth;
inherit
methods
default
layout
silent
;
# Keys are LSP methods. Values are a filter handler.
filter =
helpers.ifNonNull' filter
(
mapAttrs
(n: helpers.mkRaw)
filter
);
keys = with keys; {
inherit shuttle;
toggle_or_open = toggleOrOpen;
inherit
vsplit
split
tabe
tabnew
quit
close
;
};
};
definition = with definition; {
inherit width height;
keys = with keys; {
inherit
edit
vsplit
split
tabe
quit
close
;
};
};
rename = with rename; {
in_select = inSelect;
auto_save = autoSave;
project_max_width = projectMaxWidth;
project_max_height = projectMaxHeight;
keys = with keys; {
inherit
quit
exec
select
;
};
};
symbol_in_winbar = with symbolInWinbar; {
inherit
enable
separator
;
hide_keyword = hideKeyword;
show_file = showFile;
folder_level = folderLevel;
color_mode = colorMode;
dely = delay;
};
outline = with outline; {
win_position = winPosition;
win_width = winWidth;
auto_preview = autoPreview;
inherit detail;
auto_close = autoClose;
close_after_jump = closeAfterJump;
inherit layout;
max_height = maxHeight;
left_width = leftWidth;
keys = with keys; {
toggle_or_jump = toggleOrJump;
inherit
quit
jump
;
};
};
callhierarchy = with callhierarchy; {
inherit layout;
keys = with keys; {
inherit
edit
vsplit
split
tabe
close
quit
shuttle
;
toggle_or_req = toggleOrReq;
};
};
implement = with implement; {
inherit
enable
sign
;
virtual_text = virtualText;
inherit priority;
};
beacon = with beacon; {
inherit
enable
frequency
;
};
}
// cfg.extraOptions;
in ''
require('lspsaga').setup(${helpers.toLuaObject setupOptions})
'';
};
}

View file

@ -0,0 +1,164 @@
{
empty = {
plugins.lspsaga.enable = true;
};
defaults = {
plugins.lspsaga = {
enable = true;
ui = {
border = "single";
devicon = true;
title = true;
expand = "";
collapse = "";
codeAction = "💡";
actionfix = "";
lines = ["" "" "" "" ""];
kind = {};
impSign = "󰳛 ";
};
hover = {
maxWidth = 0.9;
maxHeight = 0.8;
openLink = "gx";
openCmd = "!chrome";
};
diagnostic = {
showCodeAction = true;
showLayout = "float";
showNormalHeight = 10;
jumpNumShortcut = true;
maxWidth = 0.8;
maxHeight = 0.6;
maxShowWidth = 0.9;
maxShowHeight = 0.6;
textHlFollow = true;
borderFollow = true;
extendRelatedInformation = false;
diagnosticOnlyCurrent = false;
keys = {
execAction = "o";
quit = "q";
toggleOrJump = "<CR>";
quitInShow = ["q" "<ESC>"];
};
};
codeAction = {
numShortcut = true;
showServerName = false;
extendGitSigns = false;
onlyInCursor = true;
keys = {
quit = "q";
exec = "<CR>";
};
};
lightbulb = {
enable = true;
sign = true;
debounce = 10;
signPriority = 40;
virtualText = true;
};
scrollPreview = {
scrollDown = "<C-f>";
scrollUp = "<C-b>";
};
finder = {
maxHeight = 0.5;
leftWidth = 0.3;
rightWidth = 0.3;
methods = {
tyd = "textDocument/typeDefinition";
};
default = "ref+imp";
layout = "float";
silent = false;
filter = {};
keys = {
shuttle = "[w";
toggleOrOpen = "o";
vsplit = "s";
split = "i";
tabe = "t";
tabnew = "r";
quit = "q";
close = "<C-c>k";
};
};
definition = {
width = 0.6;
height = 0.5;
keys = {
edit = "<C-c>o";
vsplit = "<C-c>v";
split = "<C-c>i";
tabe = "<C-c>t";
quit = "q";
close = "<C-c>k";
};
};
rename = {
inSelect = true;
autoSave = false;
projectMaxWidth = 0.5;
projectMaxHeight = 0.5;
keys = {
quit = "<C-k>";
exec = "<CR>";
select = "x";
};
};
symbolInWinbar = {
enable = true;
separator = " ";
hideKeyword = false;
showFile = true;
folderLevel = 1;
colorMode = true;
delay = 300;
};
outline = {
winPosition = "right";
winWidth = 30;
autoPreview = true;
detail = true;
autoClose = true;
closeAfterJump = false;
layout = "normal";
maxHeight = 0.5;
leftWidth = 0.3;
keys = {
toggleOrJump = "o";
quit = "q";
jump = "e";
};
};
callhierarchy = {
layout = "float";
keys = {
edit = "e";
vsplit = "s";
split = "i";
tabe = "t";
close = "<C-c>k";
quit = "q";
shuttle = "[w";
toggleOrReq = "u";
};
};
implement = {
enable = true;
sign = true;
virtualText = true;
priority = 100;
};
beacon = {
enable = true;
frequency = 7;
};
};
};
}