From df319dea276696ccfd8e8c61fe36f26f0b8ab86e Mon Sep 17 00:00:00 2001
From: EvilDragonfiend <87972842+EvilDragonfiend@users.noreply.github.com>
Date: Thu, 21 Nov 2024 14:31:16 +0900
Subject: [PATCH] Port VV expansion from Upstream Bee
---
code/__DEFINES/_helpers.dm | 5 +
code/__DEFINES/is_helpers.dm | 9 +
code/__DEFINES/keybinding.dm | 1 +
code/__DEFINES/typeids.dm | 4 +-
code/__DEFINES/vv.dm | 16 ++
code/__HELPERS/_lists.dm | 15 +-
code/_globalvars/lists/vv.dm | 17 ++
code/_onclick/click.dm | 12 +
code/controllers/globals.dm | 6 +
code/datums/datumvars.dm | 17 +-
code/datums/keybinding/admin.dm | 13 ++
code/datums/weakrefs.dm | 16 ++
code/game/atoms.dm | 5 +
code/modules/admin/admin_verbs.dm | 2 +
code/modules/admin/callproc/callproc.dm | 2 +-
code/modules/admin/holder2.dm | 3 +
code/modules/admin/tag.dm | 108 +++++++++
code/modules/admin/topic.dm | 29 +++
.../view_variables/color_matrix_editor.dm | 129 +++++++++++
.../admin/view_variables/debug_variables.dm | 209 +++++++++++-------
.../admin/view_variables/get_variables.dm | 88 +++++++-
.../modules/admin/view_variables/tag_datum.dm | 18 ++
code/modules/admin/view_variables/topic.dm | 5 +-
.../admin/view_variables/topic_basic.dm | 2 +
.../admin/view_variables/view_variables.dm | 90 ++++----
icons/misc/colortest.dmi | Bin 0 -> 352 bytes
nsv13.dme | 3 +
.../tgui/interfaces/ColorMatrixEditor.js | 68 ++++++
28 files changed, 752 insertions(+), 140 deletions(-)
create mode 100644 code/_globalvars/lists/vv.dm
create mode 100644 code/modules/admin/tag.dm
create mode 100644 code/modules/admin/view_variables/color_matrix_editor.dm
create mode 100644 code/modules/admin/view_variables/tag_datum.dm
create mode 100644 icons/misc/colortest.dmi
create mode 100644 tgui/packages/tgui/interfaces/ColorMatrixEditor.js
diff --git a/code/__DEFINES/_helpers.dm b/code/__DEFINES/_helpers.dm
index 22ca001ae4e..5fc95acb1af 100644
--- a/code/__DEFINES/_helpers.dm
+++ b/code/__DEFINES/_helpers.dm
@@ -7,3 +7,8 @@
//Returns an integer given a hex input, supports negative values "-ff"
//skips preceding invalid characters
#define hex2num(X) text2num(X, 16)
+
+// Refs contain a type id within their string that can be used to identify byond types.
+// Custom types that we define don't get a unique id, but this is useful for identifying
+// types that don't normally have a way to run istype() on them.
+#define TYPEID(thing) copytext(REF(thing), 4, 6)
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index b04cbd356e3..03391cbdf63 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -13,6 +13,15 @@
#define isweakref(D) (istype(D, /datum/weakref))
+#define isimage(thing) (istype(thing, /image))
+
+GLOBAL_VAR_INIT(magic_appearance_detecting_image, new /image) // appearances are awful to detect safely, but this seems to be the best way ~ninjanomnom
+#define isappearance(thing) (!isimage(thing) && !ispath(thing) && istype(GLOB.magic_appearance_detecting_image, thing))
+
+// The filters list has the same ref type id as a filter, but isnt one and also isnt a list, so we have to check if the thing has Cut() instead
+GLOBAL_VAR_INIT(refid_filter, TYPEID(filter(type="angular_blur")))
+#define isfilter(thing) (hascall(thing, "Cut") && TYPEID(thing) == GLOB.refid_filter)
+
// simple check whether or not a player is a guest using their key
#define IS_GUEST_KEY(key) (findtextEx(key, "Guest-", 1, 7))
diff --git a/code/__DEFINES/keybinding.dm b/code/__DEFINES/keybinding.dm
index 602660074ac..6994ab4a9a4 100644
--- a/code/__DEFINES/keybinding.dm
+++ b/code/__DEFINES/keybinding.dm
@@ -11,6 +11,7 @@
#define COMSIG_KB_ADMIN_AGHOST_DOWN "keybinding_admin_aghost_down"
#define COMSIG_KB_ADMIN_PLAYERPANEL_DOWN "keybinding_admin_playerpanelnew_down"
#define COMSIG_KB_ADMIN_INVISIMINTOGGLE_DOWN "keybinding_admin_invisimintoggle_down"
+#define COMSIG_KB_ADMIN_VIEWTAGS_DOWN "keybinding_admin_viewtags_down"
//Carbon
#define COMSIG_KB_CARBON_TOGGLETHROWMODE_DOWN "keybinding_carbon_togglethrowmode_down"
diff --git a/code/__DEFINES/typeids.dm b/code/__DEFINES/typeids.dm
index 275f7719f07..16a71266bec 100644
--- a/code/__DEFINES/typeids.dm
+++ b/code/__DEFINES/typeids.dm
@@ -3,6 +3,6 @@
#define TYPEID_NORMAL_LIST "f"
//helper macros
#define GET_TYPEID(ref) ( ( (length(ref) <= 10) ? "TYPEID_NULL" : copytext(ref, 4, -7) ) )
+// Only allowed raw ref, since this is for lists explicitly and they will get no use from it
+// Also it tends to be used in a hot context so let's be nice yes?
#define IS_NORMAL_LIST(L) (GET_TYPEID("\ref[L]") == TYPEID_NORMAL_LIST)
-
-
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index 4938ca5e772..a82fb540270 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -2,6 +2,8 @@
#define VV_TEXT "Text"
#define VV_MESSAGE "Mutiline Text"
#define VV_ICON "Icon"
+#define VV_COLOR "Color"
+#define VV_COLOR_MATRIX "Color Matrix"
#define VV_ATOM_REFERENCE "Atom Reference"
#define VV_DATUM_REFERENCE "Datum Reference"
#define VV_MOB_REFERENCE "Mob Reference"
@@ -16,13 +18,17 @@
#define VV_NEW_TYPE "New Custom Typepath"
#define VV_NEW_LIST "New List"
#define VV_NULL "NULL"
+#define VV_INFINITY "Infinity"
#define VV_RESTORE_DEFAULT "Restore to Default"
#define VV_MARKED_DATUM "Marked Datum"
+#define VV_TAGGED_DATUM "Tagged Datum"
#define VV_BITFIELD "Bitfield"
#define VV_TEXT_LOCATE "Custom Reference Locate"
#define VV_PROCCALL_RETVAL "Return Value of Proccall"
+#define VV_WEAKREF "Weak Reference Datum"
#define VV_MSG_MARKED " Marked Object"
+#define VV_MSG_TAGGED(num) " Tagged Datum #[num]"
#define VV_MSG_EDITED " Var Edited"
#define VV_MSG_DELETED " Deleted"
@@ -73,12 +79,16 @@
#define VV_HK_EXPOSE "expose"
#define VV_HK_CALLPROC "proc_call"
#define VV_HK_MARK "mark"
+#define VV_HK_TAG "tag"
#define VV_HK_ADDCOMPONENT "addcomponent"
#define VV_HK_MODIFY_TRAITS "modtraits"
#ifdef REFERENCE_TRACKING
#define VV_HK_VIEW_REFERENCES "viewreferences"
#endif
+// /datum/weakref
+#define VV_HK_WEAKREF_RESOLVE "weakref_resolve"
+
// /atom
#define VV_HK_MODIFY_TRANSFORM "atom_transform"
#define VV_HK_MODIFY_GREYSCALE "modify_greyscale"
@@ -87,6 +97,7 @@
#define VV_HK_TRIGGER_EXPLOSION "explode"
#define VV_HK_AUTO_RENAME "auto_rename"
#define VV_HK_EDIT_FILTERS "edit_filters"
+#define VV_HK_EDIT_COLOR_MATRIX "edit_color_matrix"
#define VV_HK_ADD_AI "add_ai"
// /datum/gas_mixture
@@ -143,3 +154,8 @@
// paintings
#define VV_HK_REMOVE_PAINTING "remove_painting"
+
+// Flags for debug_variable() that do little things to what we end up rendering
+
+/// ALWAYS render a reduced list, useful for fuckoff big datums that need to be condensed for the sake of client load
+#define VV_ALWAYS_CONTRACT_LIST (1<<0)
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index 001912a2b1d..d080c8f088d 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -9,6 +9,15 @@
* Misc
*/
+// Generic listoflist safe add and removal macros:
+///If value is a list, wrap it in a list so it can be used with list add/remove operations
+#define LIST_VALUE_WRAP_LISTS(value) (islist(value) ? list(value) : value)
+///Add an untyped item to a list, taking care to handle list items by wrapping them in a list to remove the footgun
+#define UNTYPED_LIST_ADD(list, item) (list += LIST_VALUE_WRAP_LISTS(item))
+///Remove an untyped item to a list, taking care to handle list items by wrapping them in a list to remove the footgun
+#define UNTYPED_LIST_REMOVE(list, item) (list -= LIST_VALUE_WRAP_LISTS(item))
+
+// Lazylist macros
#define LAZYINITLIST(L) if (!L) { L = list(); }
#define UNSETEMPTY(L) if (L && !length(L)) L = null
#define ASSOC_UNSETEMPTY(L, K) if (!length(L[K])) L -= K; //NSV13
@@ -251,8 +260,8 @@
var/list/result = new
if(skiprep)
for(var/e in first)
- if(!(e in result) && !(e in second))
- result += e
+ if(!(e in result) && !(e in second))f
+ UNTYPED_LIST_ADD(result, e)
else
result = first - second
return result
@@ -685,4 +694,4 @@
. = list()
for(var/i in L)
if(condition.Invoke(i))
- . |= i
+ . |= LIST_VALUE_WRAP_LISTS(i)
diff --git a/code/_globalvars/lists/vv.dm b/code/_globalvars/lists/vv.dm
new file mode 100644
index 00000000000..2f14bd240de
--- /dev/null
+++ b/code/_globalvars/lists/vv.dm
@@ -0,0 +1,17 @@
+// A list of all the special byond lists that need to be handled different by vv
+GLOBAL_LIST_INIT(vv_special_lists, init_special_list_names())
+
+/proc/init_special_list_names()
+ var/list/output = list()
+ var/obj/sacrifice = new
+ for(var/varname in sacrifice.vars)
+ var/value = sacrifice.vars[varname]
+ if(!islist(value))
+ if(!isdatum(value) && hascall(value, "Cut"))
+ output += varname
+ continue
+ if(isnull(locate(REF(value))))
+ output += varname
+ return output
+
+GLOBAL_LIST_INIT(color_vars, list("color"))
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index d186b39abfd..88232ecd152 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -86,6 +86,9 @@
CtrlShiftClickOn(A)
return
if(modifiers["middle"])
+ if(modifiers["ctrl"])
+ CtrlMiddleClickOn(A) // basically for vv tag datum
+ return
MiddleClickOn(A)
return
if(modifiers["shift"])
@@ -344,6 +347,15 @@
H.changeNext_move(CLICK_CD_MELEE)
else
..()
+
+/mob/proc/CtrlMiddleClickOn(atom/A)
+ // specifically made for admin feature.
+ if(check_rights_for(client, R_ADMIN))
+ client.toggle_tag_datum(A)
+ return
+ A.CtrlClick(src) // this assumes you did CtrlClick instead of MiddleClick
+ return
+
/*
Alt click
Unused except for AI
diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm
index 1e393f61fa5..b05d74eabf0 100644
--- a/code/controllers/globals.dm
+++ b/code/controllers/globals.dm
@@ -43,6 +43,12 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
return FALSE
return ..()
+/datum/controller/global_vars/vv_get_var(var_name)
+ switch(var_name)
+ if (NAMEOF(src, vars))
+ return debug_variable(var_name, list(), 0, src)
+ return debug_variable(var_name, vars[var_name], 0, src, display_flags = VV_ALWAYS_CONTRACT_LIST)
+
/datum/controller/global_vars/can_vv_get(var_name)
if(var_name == "gvars_datum_protected_varlist" || var_name == "gvars_datum_in_built_vars")
return FALSE
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index f9d39be51b9..0707320767c 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -20,21 +20,28 @@
/datum/proc/can_vv_mark()
return TRUE
-//please call . = ..() first and append to the result, that way parent items are always at the top and child items are further down
-//add separaters by doing . += "---"
+/**
+ * Gets all the dropdown options in the vv menu.
+ * When overriding, make sure to call . = ..() first and appent to the result, that way parent items are always at the top and child items are further down.
+ * Add seperators by doing VV_DROPDOWN_OPTION("", "---")
+ */
/datum/proc/vv_get_dropdown()
+ SHOULD_CALL_PARENT(TRUE)
. = list()
VV_DROPDOWN_OPTION("", "---")
VV_DROPDOWN_OPTION(VV_HK_CALLPROC, "Call Proc")
VV_DROPDOWN_OPTION(VV_HK_MARK, "Mark Object")
+ VV_DROPDOWN_OPTION(VV_HK_TAG, "Tag Datum")
VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete")
VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player")
VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element")
VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits")
-//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks!
-//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables!
-//This proc is for "high level" actions like admin heal/set species/etc/etc. The low level debugging things should go in admin/view_variables/topic_basic.dm incase this runtimes.
+/**
+ * This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks!
+ * href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables!
+ * This proc is for "high level" actions like admin heal/set species/etc/etc. The low level debugging things should go in admin/view_variables/topic_basic.dm incase this runtimes.
+ */
/datum/proc/vv_do_topic(list/href_list)
if(!usr || !usr.client || !usr.client.holder || !check_rights(NONE))
return FALSE //This is VV, not to be called by anything else.
diff --git a/code/datums/keybinding/admin.dm b/code/datums/keybinding/admin.dm
index 4a7c000d9b3..03ae8943807 100644
--- a/code/datums/keybinding/admin.dm
+++ b/code/datums/keybinding/admin.dm
@@ -98,6 +98,19 @@
user.invisimin()
return TRUE
+/datum/keybinding/admin/view_tags
+ key = "F9"
+ name = "view_tags"
+ full_name = "View Tags"
+ description = "Open the View-Tags menu"
+ keybind_signal = COMSIG_KB_ADMIN_VIEWTAGS_DOWN
+
+/datum/keybinding/admin/view_tags/down(client/user)
+ . = ..()
+ if(.)
+ return
+ user.holder?.display_tags()
+ return TRUE
/datum/keybinding/admin/dead_say
key = "F10"
diff --git a/code/datums/weakrefs.dm b/code/datums/weakrefs.dm
index c243f35f343..089055e4aa5 100644
--- a/code/datums/weakrefs.dm
+++ b/code/datums/weakrefs.dm
@@ -28,3 +28,19 @@
var/datum/D = locate(reference)
return (!QDELETED(D) && D.weak_reference == src) ? D : null
+/datum/weakref/vv_get_dropdown()
+ . = ..()
+ VV_DROPDOWN_OPTION(VV_HK_WEAKREF_RESOLVE, "Go to reference")
+
+/datum/weakref/vv_do_topic(list/href_list)
+ . = ..()
+
+ if(!.)
+ return
+
+ if(href_list[VV_HK_WEAKREF_RESOLVE])
+ if(!check_rights(NONE))
+ return
+ var/datum/R = resolve()
+ if(R)
+ usr.client.debug_variables(R)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index a9bf421feb8..78251c3d28e 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1117,6 +1117,7 @@
VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse")
VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion")
VV_DROPDOWN_OPTION(VV_HK_EDIT_FILTERS, "Edit Filters")
+ VV_DROPDOWN_OPTION(VV_HK_EDIT_COLOR_MATRIX, "Edit Color as Matrix")
VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller")
if(greyscale_colors)
VV_DROPDOWN_OPTION(VV_HK_MODIFY_GREYSCALE, "Modify greyscale colors")
@@ -1211,6 +1212,10 @@
var/client/C = usr.client
C?.open_filter_editor(src)
+ if(href_list[VV_HK_EDIT_COLOR_MATRIX] && check_rights(R_VAREDIT))
+ var/client/C = usr.client
+ C?.open_color_matrix_editor(src)
+
/atom/vv_get_header()
. = ..()
var/refid = REF(src)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index b6aedb6184e..2d331e35aa5 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -19,6 +19,7 @@ GLOBAL_PROTECT(admin_verbs_default)
/client/proc/cmd_admin_pm_panel, /*admin-pm list*/
/client/proc/stop_sounds,
/client/proc/mark_datum_mapview,
+ /client/proc/tag_datum_mapview,
/client/proc/requests,
/client/proc/fax_manager
)
@@ -83,6 +84,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/battle_royale,
/client/proc/delete_book,
/client/proc/cmd_admin_send_pda_msg,
+ /datum/admins/proc/display_tags,
/client/proc/changeranks, //NSV13 - verb to change rank structure
/client/proc/system_manager, //Nsv13 - Fleet + starsystem management
/client/proc/instance_overmap_menu, //Nsv13 - Midround ship creation.
diff --git a/code/modules/admin/callproc/callproc.dm b/code/modules/admin/callproc/callproc.dm
index b06241e919a..8a585584161 100644
--- a/code/modules/admin/callproc/callproc.dm
+++ b/code/modules/admin/callproc/callproc.dm
@@ -172,7 +172,7 @@ GLOBAL_PROTECT(LastAdminCalledProc)
if(named_arg)
named_args[named_arg] = value["value"]
else
- . += value["value"]
+ . += LIST_VALUE_WRAP_LISTS(value["value"])
if(LAZYLEN(named_args))
. += named_args
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index 694b60c13d5..45a905b8550 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -33,6 +33,9 @@ GLOBAL_PROTECT(href_token)
var/datum/filter_editor/filteriffic
+ /// A lazylist of tagged datums, for quick reference with the View Tags verb
+ var/list/tagged_datums
+
/datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
diff --git a/code/modules/admin/tag.dm b/code/modules/admin/tag.dm
new file mode 100644
index 00000000000..3c72607366c
--- /dev/null
+++ b/code/modules/admin/tag.dm
@@ -0,0 +1,108 @@
+/**
+ * Inserts the target_datum into [/datum/admins/var/tagged_datums], for later reference.
+ *
+ * Arguments:
+ * * target_datum - The datum you want to create a tag for
+ */
+/datum/admins/proc/add_tagged_datum(datum/target_datum)
+ if(LAZYFIND(tagged_datums, target_datum))
+ to_chat(owner, "[target_datum] is already tagged!")
+ return
+
+ LAZYADD(tagged_datums, target_datum)
+ RegisterSignal(target_datum, COMSIG_PARENT_QDELETING, PROC_REF(handle_tagged_del), override = TRUE)
+ to_chat(owner, "[target_datum] has been tagged.")
+
+/// Get ahead of the curve with deleting
+/datum/admins/proc/handle_tagged_del(datum/source)
+ SIGNAL_HANDLER
+
+ if(owner)
+ to_chat(owner, "Tagged datum [source] ([source.type]) has been deleted.")
+ remove_tagged_datum(source, silent = TRUE)
+
+/**
+ * Attempts to remove the specified datum from [/datum/admins/var/tagged_datums] if it exists
+ *
+ * Arguments:
+ * * target_datum - The datum you want to remove from the tagged_datums list
+ * * silent - If TRUE, won't print messages to the owner's chat
+ */
+/datum/admins/proc/remove_tagged_datum(datum/target_datum, silent=FALSE)
+ if(!istype(target_datum))
+ return
+
+ if(LAZYFIND(tagged_datums, target_datum))
+ LAZYREMOVE(tagged_datums, target_datum)
+ if(!silent)
+ to_chat(owner, "[target_datum] has been untagged.")
+ else if(!silent)
+ to_chat(owner, "[target_datum] was not already tagged.")
+
+/// Quick define for readability
+#define TAG_DEL(X) "(UNTAG)"
+#define TAG_MARK(X) "(MARK)"
+#define TAG_SIMPLE_HEALTH(X) "Health: [X.health]"
+#define TAG_CARBON_HEALTH(X) "Health: [X.health] (\
+ [X.getBruteLoss()] \
+ [X.getFireLoss()] \
+ [X.getToxLoss()] \
+ [X.getOxyLoss()]\
+ [X.getCloneLoss() ? " [X.getCloneLoss()]" : ""])"
+
+/// Display all of the tagged datums
+/datum/admins/proc/display_tags()
+ set category = "Admin.Game"
+ set name = "View Tags"
+
+ if (!istype(src, /datum/admins))
+ src = usr.client.holder
+ if (!istype(src, /datum/admins))
+ to_chat(usr, "Error: you are not an admin!")
+ return
+
+ var/index = 0
+ var/list/dat = list("
"} //TODO link to modify_transform wrapper for all matrices
- else if (istype(value, /datum))
- var/datum/DV = value
- if ("[DV]" != "[DV.type]") //if the thing as a name var, lets use it.
- item = "[VV_HTML_ENCODE(name)] [REF(value)] = [DV] [DV.type]"
- else
- item = "[VV_HTML_ENCODE(name)] [REF(value)] = [DV.type]"
-
- else if (islist(value))
- var/list/L = value
- var/list/items = list()
-
- if (L.len > 0 && !(name == "underlays" || name == "overlays" || L.len > (IS_NORMAL_LIST(L) ? VV_NORMAL_LIST_NO_EXPAND_THRESHOLD : VV_SPECIAL_LIST_NO_EXPAND_THRESHOLD)))
- for (var/i in 1 to L.len)
- var/key = L[i]
- var/val
- if (IS_NORMAL_LIST(L) && !isnum_safe(key))
- val = L[key]
- if (isnull(val)) // we still want to display non-null false values, such as 0 or ""
- val = key
- key = i
-
- items += debug_variable(key, val, level + 1, sanitize = sanitize)
-
- item = "[VV_HTML_ENCODE(name)] = /list ([L.len])