diff --git a/.vscode/settings.json b/.vscode/settings.json
index a3f7565..ca72275 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,5 +3,6 @@
"luau-lsp.require.mode": "relativeToFile",
"luau-lsp.require.directoryAliases": {
"@lune/": "~/.lune/.typedefs/0.7.11/"
- }
+ },
+ "luau-lsp.sourcemap.rojoProjectFile": "default.project.json"
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 4959c70..9a5c459 100644
--- a/README.md
+++ b/README.md
@@ -22,4 +22,5 @@ Head over to the [installation](https://github.com/1Axen/Blink/blob/main/docs/In
# Credits
Credits to [Zap](https://zap.redblox.dev/) for the range and array syntax
Credits to [ArvidSilverlock](https://github.com/ArvidSilverlock) for the float16 implementation
+Studio plugin auto completion icons are sourced from [Microsoft](https://github.com/microsoft/vscode-icons) and are under the [CC BY 4.0](https://github.com/microsoft/vscode-icons/blob/main/LICENSE) license.
Speed icons created by alkhalifi design - Flaticon
diff --git a/build/.darklua.json b/build/.darklua.json
index 63559b3..d3c28f4 100644
--- a/build/.darklua.json
+++ b/build/.darklua.json
@@ -26,7 +26,7 @@
{
"identifier": "VERSION",
"rule": "inject_global_value",
- "value": "0.6.4"
+ "value": "0.7.0"
}
]
}
\ No newline at end of file
diff --git a/build/build.bat b/build/build.bat
index b910cfa..720cafc 100644
--- a/build/build.bat
+++ b/build/build.bat
@@ -27,8 +27,8 @@ echo Zipping files
echo Packaging plugin
cd ../plugin
copy "..\build\.darklua.json" ".\.darklua.json"
-darklua process "./src/init.luau" "./bundle/init.server.lua"
-rojo build --output "../release/Plugin-%VERSION%.rbxmx"
+darklua process "./src/init.server.luau" "./bundle/init.server.lua"
+rojo build bundle.project.json --output "../release/Plugin-%VERSION%.rbxmx"
cd ../build
diff --git a/default.project.json b/default.project.json
new file mode 100644
index 0000000..6d46e72
--- /dev/null
+++ b/default.project.json
@@ -0,0 +1,6 @@
+{
+ "name": "Blink",
+ "tree": {
+ "$path": "plugin/src"
+ }
+ }
\ No newline at end of file
diff --git a/docs/Using.md b/docs/Using.md
index d52f87a..253efe0 100644
--- a/docs/Using.md
+++ b/docs/Using.md
@@ -49,6 +49,7 @@ Blink supports the following primitives:
|CFrame |24 Bytes |No |N/A |N/A |
|Color3 |12 Bytes |No |N/A |N/A |
|Instance|4 Bytes |No |N/A |N/A |
+|unknown |N/A |No |N/A |N/A |
Arrays can be defined by appending `[SIZE]` or `[[MIN]..[MAX]]` after the primitive type
Primitives can be constrained to ranges by writing `([MIN]..[MAX])` after the primitive type. Ranges are inclusive.
diff --git a/plugin/.darklua.json b/plugin/.darklua.json
index 63559b3..d3c28f4 100644
--- a/plugin/.darklua.json
+++ b/plugin/.darklua.json
@@ -26,7 +26,7 @@
{
"identifier": "VERSION",
"rule": "inject_global_value",
- "value": "0.6.4"
+ "value": "0.7.0"
}
]
}
\ No newline at end of file
diff --git a/plugin/build.bat b/plugin/build.bat
index 1422a13..d5faaa3 100644
--- a/plugin/build.bat
+++ b/plugin/build.bat
@@ -1,5 +1,12 @@
@echo Off
+copy ".\src\Error.rbxmx" ".\bundle\Error.rbxmx"
+copy ".\src\Widget.rbxmx" ".\bundle\Widget.rbxmx"
copy "..\build\.darklua.json" ".\.darklua.json"
-darklua process "./src/init.luau" "./bundle/init.server.lua"
-rojo build --output "%LocalAppData%\Roblox\Plugins\Blink.rbxmx"
\ No newline at end of file
+
+darklua process "./src/init.server.luau" "./bundle/init.server.lua"
+rojo build bundle.project.json --output "%LocalAppData%\Roblox\Plugins\Blink.rbxmx"
+
+cd ..
+rojo sourcemap --output sourcemap.json --include-non-scripts
+cd plugin
\ No newline at end of file
diff --git a/plugin/default.project.json b/plugin/bundle.project.json
similarity index 100%
rename from plugin/default.project.json
rename to plugin/bundle.project.json
diff --git a/plugin/bundle/Error.rbxm b/plugin/bundle/Error.rbxm
deleted file mode 100644
index 5130a73..0000000
Binary files a/plugin/bundle/Error.rbxm and /dev/null differ
diff --git a/plugin/bundle/Error.rbxmx b/plugin/bundle/Error.rbxmx
new file mode 100644
index 0000000..5617198
--- /dev/null
+++ b/plugin/bundle/Error.rbxmx
@@ -0,0 +1,188 @@
+
+ true
+ null
+ nil
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.156862751
+ 0.156862751
+ 0.156862751
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Error
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ false
+ 3
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxasset://fonts/families/Ubuntu.json
+ 400
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ Text
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ true
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+ Label
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 16
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 0
+ true
+ 1
+
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+ -1
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin/bundle/Widget.rbxm b/plugin/bundle/Widget.rbxm
deleted file mode 100644
index e040bb1..0000000
Binary files a/plugin/bundle/Widget.rbxm and /dev/null differ
diff --git a/plugin/bundle/Widget.rbxmx b/plugin/bundle/Widget.rbxmx
new file mode 100644
index 0000000..92df568
--- /dev/null
+++ b/plugin/bundle/Widget.rbxmx
@@ -0,0 +1,4013 @@
+
+ true
+ null
+ nil
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.156862751
+ 0.156862751
+ 0.156862751
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ true
+ false
+ false
+ true
+ 0
+ Widget
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.176470593
+ 0.176470593
+ 0.176470593
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Side
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ -208
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 250
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 2
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ rbxassetid://5051528605
+
+ 0
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0.699999988
+ true
+ 0
+ Shadow
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 1
+ 0
+ 0
+ 0
+
+ 0
+ [null]
+ 0
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 8
+ 1
+ 0
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ true
+ false
+ false
+
+ 1
+ 1
+ 1
+
+ 1
+ true
+ 0
+ Content
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ Padding
+
+
0
+ 10
+
+
+ 0
+ 10
+
+
+ 0
+ 10
+
+
+ 0
+ 10
+
+ -1
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Top
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Title
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 22
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Search
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 200
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 0
+
+ 0
+
+ 0.0980392173
+ 0.0980392173
+ 0.0980392173
+
+ false
+ true
+ 0
+ UIStroke
+ -1
+
+ 1
+ 0
+
+
+ -
+
+ true
+
+ 0.5
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ true
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 500
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ false
+ Input
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0.43921569
+ 0.43921569
+ 0.43921569
+
+ Search
+
+ 0.5
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+ true
+
+ 0.899999976
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+
+
+ 1
+ 1
+ 1
+
+ 0
+ true
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 1
+ true
+ 1
+
+
+
+
+ -
+
+
+ 0
+ false
+ 1
+ 0
+ 0
+ 0
+ UIListLayout
+
+
0
+ 10
+
+ 2
+ -1
+
+ 1
+ 1
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ true
+ false
+ false
+ true
+ 0
+ Files
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ 1
+ 0
+ 0
+ 0
+ UIListLayout
+
+
0
+ 10
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ File
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 24
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ Title
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+ Example File
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 16
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 1
+ true
+ 1
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Buttons
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ 0
+ 2
+ 0
+ 0
+ UIListLayout
+
+
0
+ 5
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.117647059
+ 0.117647059
+ 0.117647059
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://16483084978
+
+ 1
+ 1
+ 1
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0
+ true
+ 0
+ false
+ Delete
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ [null]
+ 0
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+ 0
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 1
+ 0
+
+ 0
+ false
+ 0
+ UIAspectRatioConstraint
+ -1
+
+
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.117647059
+ 0.117647059
+ 0.117647059
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://16483089645
+
+ 1
+ 1
+ 1
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0
+ true
+ 0
+ false
+ Edit
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ [null]
+ 0
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+ 0
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 1
+ 0
+
+ 0
+ false
+ 0
+ UIAspectRatioConstraint
+ -1
+
+
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.117647059
+ 0.117647059
+ 0.117647059
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://16494932101
+
+ 1
+ 1
+ 1
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0
+ true
+ 0
+ false
+ Generate
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ [null]
+ 0
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+ 0
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 1
+ 0
+
+ 0
+ false
+ 0
+ UIAspectRatioConstraint
+ -1
+
+
+
+
+
+
+
+
+ -
+
+ false
+
+ 0
+ 1
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Bottom
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 1
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 22
+
+ 0
+ -1
+ 0
+
+ true
+ 2
+
+ -
+
+
+ 0
+ false
+ 1
+ 0
+ 0
+ 0
+ UIListLayout
+
+
0
+ 10
+
+ 2
+ -1
+
+ 2
+ 0
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 2
+ Buttons
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ 0
+ 1
+ 1
+ 0
+ UIListLayout
+
+
0
+ 10
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.117647059
+ 0.117647059
+ 0.117647059
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 1
+ 1
+
+
+ -1
+ false
+ Save
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ Save
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 2
+ 1
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.117647059
+ 0.117647059
+ 0.117647059
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ false
+ Cancel
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ Cancel
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 2
+ 1
+ false
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Generate
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 72
+
+ 0
+ -1
+ 0
+
+ false
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 0
+
+ 0
+
+ 0.0980392173
+ 0.0980392173
+ 0.0980392173
+
+ false
+ true
+ 0
+ UIStroke
+ -1
+
+ 1
+ 0
+
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+ -1
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 1
+ 1
+
+
+ -1
+ Hint
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ true
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 30
+
+ 0
+ -1
+
+ Selected
+ReplicatedStroage.bla.bla.bla]]>
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 1
+ true
+ 2
+ 1
+ true
+ 1
+
+
+ -
+
+
+ 0
+ false
+ 1
+ 0
+ 0
+ 0
+ UIListLayout
+
+
0
+ 2
+
+ 2
+ -1
+
+ 0
+ 3
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 1
+ Buttons
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0.899999976
+ 0
+ 0
+ 18
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.196078435
+ 0.196078435
+ 0.196078435
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 1
+ 1
+
+
+ -1
+ false
+ Cancel
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0.899999976
+ 0
+ 0
+ 18
+
+ 0
+ -1
+ 0
+
+ Cancel
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 2
+ 1
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.196078435
+ 0.196078435
+ 0.196078435
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 1
+ 1
+
+
+ -1
+ false
+ Generate
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0.899999976
+ 0
+ 0
+ 18
+
+ 0
+ -1
+ 0
+
+ Generate
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0.5
+ 0
+ false
+ 2
+ 1
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+
+ -
+
+
+ 0
+ false
+ 0
+ 1
+ 1
+ 0
+ UIListLayout
+
+
0
+ 10
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 1
+ Save
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 22
+
+ 0
+ -1
+ 0
+
+ false
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 0
+
+ 0
+
+ 0.0980392173
+ 0.0980392173
+ 0.0980392173
+
+ false
+ true
+ 0
+ UIStroke
+ -1
+
+ 1
+ 0
+
+
+ -
+
+ true
+
+ 0.5
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ true
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 500
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ false
+ Input
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0.43921569
+ 0.43921569
+ 0.43921569
+
+ Name
+
+ 0.5
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+ true
+
+ 0.899999976
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+
+
+ 1
+ 1
+ 1
+
+ 0
+ true
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 1
+ true
+ 1
+
+
+
+
+
+ -
+
+ true
+
+ 1
+ 0
+
+
+ true
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://16482966093
+
+ 1
+ 1
+ 1
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0
+ true
+ 0
+ false
+ Expand
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 1
+ -10
+ 0
+ 10
+
+
+ 0
+ [null]
+ 0
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 22
+ 1
+ 0
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+ 0
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+ -
+
+ 1
+ 0
+
+ 0
+ false
+ 0
+ UIAspectRatioConstraint
+ -1
+
+
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Editor
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 42
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ -42
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Lines
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 42
+ 100
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 8
+
+
+ 0
+ 4
+
+
+ 0
+ 4
+
+
+ 0
+ 8
+
+ -1
+
+
+
+ -
+
+
+ 0
+ false
+ 1
+ 1
+ 0
+ 0
+ UIListLayout
+
+
0
+ 3
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxasset://fonts/families/GothamSSm.json
+ 400
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ Line
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 16
+
+ 0
+ -1
+
+ 000
+
+ 0.470588267
+ 0.470588267
+ 0.470588267
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 1
+ 1
+ true
+ 1
+
+
+
+ -
+
+
+ 0
+ false
+ 0
+ 1
+ 1
+ 0
+ UIListLayout
+
+
0
+ 0
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Text
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 20
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 100
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+ -1
+
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ 2
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 0
+ 1.20000005
+
+
+ -1
+ true
+ Input
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0.699999988
+ 0.699999988
+ 0.699999988
+
+
+
+ 0
+ 0
+ 0
+ 0
+
+ true
+ [null]
+ 0
+ true
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+ true
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+
+
+ 1
+ 1
+ 1
+
+ 0
+ true
+ false
+ 16
+
+ 0
+ 0
+ 0
+
+ 1
+ 1
+ 0
+ false
+ 0
+ 0
+ true
+ 2
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Cursor
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 1
+ 0
+ 14
+
+ 0
+ -1
+ 0
+
+ false
+ 4
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 0
+ 1.20000005
+
+
+ -1
+ Display
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ true
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 16
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 0
+ true
+ 0
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Selection
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ -1
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ 1
+ 1
+ 0
+ 0
+ UIListLayout
+
+
0
+ 0
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.200000018
+ 0.411764741
+ 0.627451003
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Line
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 19
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.200000018
+ 0.411764741
+ 0.627451003
+
+ 0.600000024
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Fill
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+
+
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0
+ 0
+ 0
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Overlay
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 2
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Completion
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0.252777785
+ 0
+ 0.0250000004
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 200
+ 0
+ 0
+
+ 0
+ -1
+ 0
+
+ false
+ 5
+
+ -
+
+ 0
+
+ 0
+
+ 0.470588267
+ 0.470588267
+ 0.470588267
+
+ false
+ true
+ 0
+ UIStroke
+ -1
+
+ 1
+ 0
+
+
+ -
+
+
+ 0
+
+
0
+ 2
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Option
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 16
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ rbxassetid://16506695241
+
+ 1
+ 1
+ 1
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0
+ true
+ 0
+ Icon
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ 1
+ [null]
+ 0
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 16
+ 0
+ 16
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 500
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ Text
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 20
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 200
+ 1
+ 0
+
+ 0
+ -1
+
+ struct
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 1
+ true
+ 1
+
+
+ -
+
+
+ 0
+ false
+ 0
+ 1
+ 1
+ 0
+ UIListLayout
+
+
0
+ 4
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 0
+
+
+ 0
+ 5
+
+
+ 0
+ 5
+
+
+ 0
+ 0
+
+ -1
+
+
+
+ -
+
+
+ 0
+
+
0
+ 4
+
+ false
+ UICorner
+ -1
+
+
+
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 2
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 2
+
+ -1
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin/bundle/init.server.lua b/plugin/bundle/init.server.lua
index dcf7218..283e4bd 100644
--- a/plugin/bundle/init.server.lua
+++ b/plugin/bundle/init.server.lua
@@ -55,6 +55,48 @@ do
return State
end
function __DARKLUA_BUNDLE_MODULES.b()
+ local Table = {}
+
+ function Table.MergeArrays(a, b)
+ local Array = table.create(#a + #b)
+
+ for _, Element in a do
+ table.insert(Array, Element)
+ end
+ for _, Element in b do
+ table.insert(Array, Element)
+ end
+
+ return Array
+ end
+ function Table.MergeDictionaries(a, b)
+ local Dictionary = table.clone(a)
+
+ for Key, Value in b do
+ if Dictionary[Key] then
+ warn(`Key "{Key}" already exists in the first dictionary.`)
+
+ continue
+ end
+
+ Dictionary[Key] = Value
+ end
+
+ return Dictionary
+ end
+ function Table.GetDictionaryKeys(Dictionary)
+ local Keys = {}
+
+ for Key in Dictionary do
+ table.insert(Keys, Key)
+ end
+
+ return Keys
+ end
+
+ return Table
+ end
+ function __DARKLUA_BUNDLE_MODULES.c()
local stdio
if not true then
@@ -82,6 +124,7 @@ do
yellow = 'rgb(255, 255, 0)',
}
local Error = {
+ OnEmit = nil,
LexerUnexpectedToken = 1001,
ParserUnexpectedEndOfFile = 2001,
ParserUnexpectedToken = 2002,
@@ -97,6 +140,7 @@ do
AnalyzeUnknownReference = 3007,
AnalyzeInvalidRangeType = 3008,
AnalyzeInvalidRange = 3009,
+ AnalyzeDuplicateDeclaration = 3010,
}
Error.__index = Error
@@ -122,6 +166,7 @@ do
Content ..= `\n{INDENT}\u{250c}\u{2500}{SPACE}input.blink`
return setmetatable({
+ Labels = {},
Source = string.split(Source, '\n'),
Message = Content,
}, Error)
@@ -155,6 +200,12 @@ do
function Error.Primary(self, Span, Text)
local Slice = self:Slice(Span)
+ table.insert(self.Labels, {
+ Span = Span,
+ Text = Text,
+ Type = 'Primary',
+ })
+
self.Message ..= `\n{Color(string.format('%03i', Slice.Line), 'blue')}{SPACE}\u{2502}{SPACE}{Slice.Text}`
self.Message ..= `\n{INDENT}\u{2502}{SPACE}{string.rep(SPACE, Slice.Spaces)}{Color(`{string.rep('^', Slice.Underlines)}{SPACE}{Fix(Text)}`, 'red')}`
@@ -163,47 +214,67 @@ do
function Error.Secondary(self, Span, Text)
local Slice = self:Slice(Span)
+ table.insert(self.Labels, {
+ Span = Span,
+ Text = Text,
+ Type = 'Secondary',
+ })
+
self.Message ..= `\n{Color(string.format('%03i', Slice.Line), 'blue')}{SPACE}\u{2502}{SPACE}{Slice.Text}`
self.Message ..= `\n{INDENT}\u{2502}{SPACE}{string.rep(SPACE, Slice.Spaces)}{Color(`{string.rep('^', Slice.Underlines)}{SPACE}{Fix(Text)}`, 'blue')}`
return self
end
function Error.Emit(self)
+ local OnEmit = Error.OnEmit
+
+ if OnEmit then
+ OnEmit(self.Labels)
+ end
+
error(self.Message, 2)
end
return Error
end
- function __DARKLUA_BUNDLE_MODULES.c()
- local Error = __DARKLUA_BUNDLE_MODULES.load('b')
- local Primitives = {
- u8 = true,
- u16 = true,
- u32 = true,
- i8 = true,
- i16 = true,
- i32 = true,
- f16 = true,
- f32 = true,
- f64 = true,
- boolean = true,
- string = true,
- vector = true,
- buffer = true,
- Color3 = true,
- CFrame = true,
- Instance = true,
- }
- local Keywords = {
- map = true,
- type = true,
- enum = true,
- struct = true,
- event = true,
- ['function'] = true,
- scope = true,
- option = true,
+ function __DARKLUA_BUNDLE_MODULES.d()
+ return {
+ Keywords = {
+ map = true,
+ type = true,
+ enum = true,
+ struct = true,
+ event = true,
+ ['function'] = true,
+ scope = true,
+ option = true,
+ },
+ Primtives = {
+ u8 = true,
+ u16 = true,
+ u32 = true,
+ i8 = true,
+ i16 = true,
+ i32 = true,
+ f16 = true,
+ f32 = true,
+ f64 = true,
+ boolean = true,
+ string = true,
+ vector = true,
+ buffer = true,
+ Color3 = true,
+ CFrame = true,
+ Instance = true,
+ unknown = true,
+ },
}
+ end
+ function __DARKLUA_BUNDLE_MODULES.e()
+ local Error = __DARKLUA_BUNDLE_MODULES.load('c')
+ local Settings = __DARKLUA_BUNDLE_MODULES.load('d')
+ local Primitives = Settings.Primtives
+ local Keywords = Settings.Keywords
local Booleans = {
['true'] = true,
['false'] = true,
@@ -390,7 +461,7 @@ do
return Lexer
end
- function __DARKLUA_BUNDLE_MODULES.d()
+ function __DARKLUA_BUNDLE_MODULES.f()
return {
DeepClone = function(Table)
local function Clone(Source)
@@ -411,11 +482,10 @@ do
end,
}
end
- function __DARKLUA_BUNDLE_MODULES.e()
- local Lexer = __DARKLUA_BUNDLE_MODULES.load('c')
- local Error = __DARKLUA_BUNDLE_MODULES.load('b')
- local Table = __DARKLUA_BUNDLE_MODULES.load('d')
- local Errors = {DuplicateDeclaration = 3001}
+ function __DARKLUA_BUNDLE_MODULES.g()
+ local Lexer = __DARKLUA_BUNDLE_MODULES.load('e')
+ local Error = __DARKLUA_BUNDLE_MODULES.load('c')
+ local Table = __DARKLUA_BUNDLE_MODULES.load('f')
local RangePrimitives = {
u8 = true,
u16 = true,
@@ -430,7 +500,26 @@ do
vector = true,
buffer = true,
}
+ local OptionalPrimitives = {
+ u8 = true,
+ u16 = true,
+ u32 = true,
+ i8 = true,
+ i16 = true,
+ i32 = true,
+ f16 = true,
+ f32 = true,
+ f64 = true,
+ boolean = true,
+ string = true,
+ vector = true,
+ buffer = true,
+ Color3 = true,
+ CFrame = true,
+ Instance = true,
+ }
local OptionsTypes = {
+ TypesOutput = 'String',
ClientOutput = 'String',
ServerOutput = 'String',
FutureLibrary = 'String',
@@ -511,44 +600,49 @@ do
end,
}
- local function GetAttributeRange(Token, Array, Source)
- local Lower, Upper
+ local function GetTypeRange(Token, Array, Source)
+ local Value = Token.Value
- if Array then
- Lower, Upper = string.match(Token.Value, `^%[({Number})..({Number})]`)
- Lower = Lower or string.match(Token.Value, `^%[({Number})]`)
+ local function ThrowMalformedRange()
+ Error.new(Error.AnalyzeInvalidRange, Source, 'Malformed range'):Primary(Token, 'Unable to parse range'):Emit()
+ error('Unable to parse range')
+ end
- local Size = tonumber(Lower) or -1
+ local Single = Array and string.match(Value, '%[(%d+)%]') or string.match(Value, '%((%d+)%)')
- if Size < 0 then
- Error.new(Error.AnalyzeInvalidRange, Source, 'Invalid array size'):Primary(Token, 'Array cannot be smaller than 0 elements'):Emit()
+ if Single then
+ local Number = (tonumber(Single))
+
+ if not Number then
+ ThrowMalformedRange()
end
- if not Upper then
- return (NumberRange.new(Size))
+ if Array and Number < 0 then
+ Error.new(Error.AnalyzeInvalidRange, Source, 'Invalid array size'):Primary(Token, 'Array cannot be smaller than 0 elements'):Emit()
end
- else
- Lower, Upper = string.match(Token.Value, `^%(({Number})..({Number})%)`)
- if not Upper then
- Lower = string.match(Token.Value, `^%(({Number})%)`)
- end
- end
- if not Lower then
- error(`Unexpected error while trying to parse range.`)
+ return NumberRange.new(Number, Number)
end
- local Min = tonumber(Lower) or 0
- local Max
+ local Lower = Array and string.match(Value, '%[(%d+)') or string.match(Value, '%((%d+)')
+ local Upper = Array and string.match(Value, '(%d+)%]') or string.match(Value, '(%d+)%)')
- if Upper then
- Max = tonumber(Upper) or 0
+ if Lower and Upper then
+ local Minimum = (tonumber(Lower))
+ local Maximum = (tonumber(Upper))
- if Min >= Max then
+ if not Minimum or not Maximum then
+ ThrowMalformedRange()
+ end
+ if Minimum >= Maximum then
Error.new(Error.AnalyzeInvalidRange, Source, 'Invalid range'):Primary(Token, 'Maximum must be greater than minimum'):Emit()
end
+
+ return NumberRange.new(Minimum, Maximum)
end
- return NumberRange.new(Min, Max)
+ ThrowMalformedRange()
+
+ return NumberRange.new(0, 0)
end
local Parser = {}
@@ -630,6 +724,7 @@ do
return {
Type = 'Body',
Value = self:Declarations(),
+ Tokens = {},
}
end
function Parser.Options(self)
@@ -679,44 +774,53 @@ do
return Declarations
end
function Parser.Declaration(self)
- local Keyword = self:Consume('Keyword')
+ local Keyword = self:Consume('Keyword').Value
local Identifier = self:Consume('Identifier')
self:Consume('Assign')
- local Type = KeywordTypes[Keyword.Value]
+ local Type = KeywordTypes[Keyword]
local Duplicates = self[Type]
local ScopedIdentifier = `{self.Scope and `{self.Scope}.` or ''}{Identifier.Value}`
local Duplicate = Duplicates[ScopedIdentifier]
if Duplicate then
- Error.new(Errors.DuplicateDeclaration, self.Source, `Duplicate {Keyword.Value} "{Identifier.Value}"`):Secondary(Duplicate.Identifier, 'Previously delcared here'):Primary(Identifier, 'Duplicate declaration here'):Emit()
+ Error.new(Error.AnalyzeDuplicateDeclaration, self.Source, `Duplicate {Keyword} "{Identifier.Value}"`):Secondary(Duplicate.Identifier, 'Previously delcared here'):Primary(Identifier, 'Duplicate declaration here'):Emit()
end
local Declaration
- if Keyword.Value == 'map' then
+ if Keyword == 'map' then
Declaration = self:MapDeclaration(Identifier)
- elseif Keyword.Value == 'type' then
+ elseif Keyword == 'type' then
Declaration = self:TypeDeclaration(Identifier)
- elseif Keyword.Value == 'enum' then
+ elseif Keyword == 'enum' then
Declaration = self:EnumDeclaration(Identifier)
- elseif Keyword.Value == 'struct' then
+ elseif Keyword == 'struct' then
Declaration = self:StructDeclaration(Identifier)
- elseif Keyword.Value == 'event' then
+ elseif Keyword == 'event' then
Declaration = self:EventDeclaration(Identifier)
- elseif Keyword.Value == 'function' then
+ elseif Keyword == 'function' then
Declaration = self:FunctionDeclaration(Identifier)
- elseif Keyword.Value == 'scope' then
+ elseif Keyword == 'scope' then
Declaration = self:ScopeDeclaration(Identifier)
end
if not Declaration then
- error(`{Keyword.Value} has no declaration handler.`)
+ error(`{Keyword} has no declaration handler.`)
end
if Duplicates == self.Types and (self:GetSafeLookAhead().Type == 'Optional') then
- self:Consume('Optional')
+ local Optional = self:Consume('Optional')
+
+ if Declaration.Type == 'TypeDeclaration' then
+ local Primitive = (Declaration).Value.Primitive
+
+ if Primitive == 'unknown' then
+ Error.new(Error.AnalyzeInvalidOptionalType, self.Source, `Invalid optional type`):Primary(Optional, `"unknown" cannot be optional`):Emit()
+ end
+ end
Declaration.Value.Optional = true
+ Declaration.Tokens.Optional = Optional
end
Duplicates[ScopedIdentifier] = {
@@ -743,6 +847,8 @@ do
Declaration = Table.DeepClone(Reference)
Declaration.Value.Identifier = Identifier.Value
+ Declaration.Tokens.Identifier = Identifier
+ Declaration.Tokens.Value = LookAhead
else
Primitive = self:Consume('Primitive')
Declaration = {
@@ -753,6 +859,10 @@ do
Primitive = Primitive.Value,
Optional = false,
},
+ Tokens = {
+ Identifier = Identifier,
+ Value = Primitive,
+ },
}
if Primitive.Value == 'Instance' then
@@ -819,6 +929,7 @@ do
Enums = Enums,
Optional = false,
},
+ Tokens = {Identifier = Identifier},
}
end
function Parser.MapDeclaration(self, Identifier)
@@ -878,6 +989,11 @@ do
Array = Array,
Optional = false,
},
+ Tokens = {
+ Identifier = Identifier,
+ Key = Key,
+ Value = Value,
+ },
}
end
function Parser.StructDeclaration(self, Identifier)
@@ -926,6 +1042,7 @@ do
Fields = Fields,
Optional = false,
},
+ Tokens = {Identifier = Identifier},
}
end
function Parser.TupleDeclaration(self, Identifier)
@@ -961,6 +1078,7 @@ do
Values = Values,
Optional = false,
},
+ Tokens = {Identifier = Identifier},
}
end
function Parser.EventDeclaration(self, Identifier)
@@ -981,6 +1099,10 @@ do
Data = nil,
Optional = false,
},
+ Tokens = {
+ Identifier = Identifier,
+ Fields = {},
+ },
}
for Index, Entry in EventStructure do
@@ -992,9 +1114,10 @@ do
local Value
local Assign = self:Consume('Assign')
+ local Token = self:GetSafeLookAhead()
if Entry.Key ~= 'Data' then
- local Token = self:Consume(Entry.Type)
+ Token = self:Consume(Entry.Type)
if not table.find(Entry.Values, Token.Value) then
Error.new(Error.ParserUnexpectedToken, self.Source, `Unexpected token`):Primary(Token, `Expected one of "{table.concat(Entry.Values, '" or "')}", found "{Token.Value}"`):Emit()
@@ -1002,9 +1125,7 @@ do
Value = Token.Value
else
- local Token = self.LookAhead
-
- if not Token then
+ if not self.LookAhead then
Error.new(Error.ParserExpectedExtraToken, self.Source, `Expected a token`):Primary(Assign, `Expected a value to follow after assignment`):Emit()
break
@@ -1018,6 +1139,11 @@ do
Event.Value[Entry.Key] = Value
+ table.insert(Event.Tokens, {
+ Field = Key,
+ Value = Token,
+ })
+
if Index ~= #EventStructure then
self:Consume('Comma')
end
@@ -1044,6 +1170,10 @@ do
Data = nil,
Return = nil,
},
+ Tokens = {
+ Identifier = Identifier,
+ Fields = {},
+ },
}
for Index, Entry in FunctionStructure do
@@ -1055,9 +1185,10 @@ do
local Value
local Assign = self:Consume('Assign')
+ local Token = self:GetSafeLookAhead()
if Entry.Key ~= 'Data' and Entry.Key ~= 'Return' then
- local Token = self:Consume(Entry.Type)
+ Token = self:Consume(Entry.Type)
if not table.find(Entry.Values, Token.Value) then
Error.new(Error.ParserUnexpectedToken, self.Source, `Unexpected token`):Primary(Token, `Expected one of "{table.concat(Entry.Values, '" or "')}", found "{Token.Value}"`):Emit()
@@ -1065,9 +1196,7 @@ do
Value = Token.Value
else
- local Token = self.LookAhead
-
- if not Token then
+ if not self.LookAhead then
Error.new(Error.ParserExpectedExtraToken, self.Source, `Expected a token`):Primary(Assign, `Expected a value to follow after assignment`):Emit()
break
@@ -1081,6 +1210,11 @@ do
Function.Value[Entry.Key] = Value
+ table.insert(Function.Tokens, {
+ Field = Key,
+ Value = Token,
+ })
+
if Index ~= #FunctionStructure then
self:Consume('Comma')
end
@@ -1107,6 +1241,7 @@ do
Optional = false,
Declarations = Declarations,
},
+ Tokens = {Identifier = Identifier},
}
self.Scope = Scope
@@ -1162,9 +1297,9 @@ do
Error.new(Error.AnalyzeInvalidRangeType, self.Source, 'Type does not support ranges'):Primary(LookAhead, `"{Primitive.Value}" does not support ranges`):Emit()
end
- Range = GetAttributeRange(LookAhead, false, self.Source)
+ Range = GetTypeRange(LookAhead, false, self.Source)
elseif Type == 'Array' then
- Array = GetAttributeRange(LookAhead, true, self.Source)
+ Array = GetTypeRange(LookAhead, true, self.Source)
end
self:Consume(Type)
@@ -1188,8 +1323,9 @@ do
elseif Token.Type == 'Primitive' then
Declaration = self:TypeDeclaration(Identifier)
elseif Token.Type == 'Identifier' then
- local Type = self:Consume('Identifier')
- local Reference = self:GetReference(Type)
+ self:Consume('Identifier')
+
+ local Reference = self:GetReference(Token)
local Array, Range = self:GetTypeAttributes()
Declaration = {
@@ -1202,6 +1338,7 @@ do
Range = Range,
Optional = false,
},
+ Tokens = {Identifier = Identifier},
}
end
if not Declaration then
@@ -1213,6 +1350,14 @@ do
if self:GetSafeLookAhead().Type == 'Optional' then
Optional = self:Consume('Optional')
Declaration.Value.Optional = true
+
+ if Token.Type == 'Identifier' then
+ local Type = (self:GetReference(Token))
+
+ if Type.Value.Primitive == 'unknown' then
+ Error.new(Error.AnalyzeInvalidOptionalType, self.Source, `Invalid optional type`):Secondary(Type.Tokens.Value, 'Declared here'):Primary(Optional, `{Type.Value.Identifier} is of type "unknown", "unknown" cannot be optional`):Emit()
+ end
+ end
end
return Declaration, Optional
@@ -1220,7 +1365,459 @@ do
return Parser
end
- function __DARKLUA_BUNDLE_MODULES.f()
+ function __DARKLUA_BUNDLE_MODULES.h()
+ local Hook = {}
+ local IndentKeywords = {
+ '{\n',
+ }
+
+ local function ShouldAutoIndent(Text, Cursor)
+ for Index, Keyword in IndentKeywords do
+ local Position = (Cursor - #Keyword)
+
+ if Position <= 0 then
+ continue
+ end
+
+ local Previous = string.sub(Text, Position, Cursor - 1)
+
+ if Previous == Keyword then
+ return true
+ end
+ end
+
+ return false
+ end
+ local function GetLineIndentation(Line)
+ return #((string.match(Line, '^\t*')))
+ end
+ local function GetCurrentLine(Text, Cursor)
+ local Line = 0
+ local Position = 0
+ local Slices = string.split(Text, '\n')
+
+ for Index, Slice in Slices do
+ Position += (#Slice + 1)
+
+ if Cursor <= Position then
+ Line = Index
+
+ break
+ end
+ end
+
+ return Line, Slices
+ end
+
+ function Hook.OnSourceChanged(Text, Cursor, Gain)
+ if Gain ~= 1 then
+ return Text, Cursor
+ end
+
+ local CanIndent = false
+ local AdditionalIndent = 0
+ local Line, Lines = GetCurrentLine(Text, Cursor)
+ local Current = Lines[Line]
+ local Previous = Lines[Line - 1]
+ local JustReached = (Previous and Current == '')
+
+ if ShouldAutoIndent(Text, Cursor) then
+ CanIndent = true
+ AdditionalIndent = 1
+ elseif JustReached then
+ if GetLineIndentation(Previous) > 0 then
+ CanIndent = true
+ end
+ end
+ if not CanIndent then
+ return Text, Cursor
+ end
+
+ local Indentation = GetLineIndentation(Previous) + AdditionalIndent
+
+ Text = string.sub(Text, 1, Cursor - 1) .. string.rep('\t', Indentation) .. string.sub(Text, Cursor)
+
+ return Text, Cursor + Indentation
+ end
+
+ return Hook
+ end
+ function __DARKLUA_BUNDLE_MODULES.i()
+ local TextService = game:GetService('TextService')
+ local RunService = game:GetService('RunService')
+ local Table = __DARKLUA_BUNDLE_MODULES.load('b')
+ local State = __DARKLUA_BUNDLE_MODULES.load('a')
+ local Lexer = __DARKLUA_BUNDLE_MODULES.load('e')
+ local Parser = __DARKLUA_BUNDLE_MODULES.load('g')
+ local Settings = __DARKLUA_BUNDLE_MODULES.load('d')
+ local Error = __DARKLUA_BUNDLE_MODULES.load('c')
+ local StylingHooks = {
+ __DARKLUA_BUNDLE_MODULES.load('h'),
+ }
+ local ICONS = {
+ Event = 'rbxassetid://16506730516',
+ Field = 'rbxassetid://16506725096',
+ Snippet = 'rbxassetid://16506712161',
+ Keyword = 'rbxassetid://16506695241',
+ Variable = 'rbxassetid://16506719167',
+ Primitive = 'rbxassetid://16506695241',
+ }
+ local COLORS = {
+ Text = Color3 .fromHex('#FFFFFF'),
+ Keyword = Color3 .fromHex('#6796E6'),
+ Primitive = Color3 .fromHex('#4EC9B0'),
+ Identifier = Color3 .fromHex('#9CDCFE'),
+ Class = Color3 .fromHex('#B5CEA8'),
+ Number = Color3 .fromHex('#B5CEA8'),
+ String = Color3 .fromHex('#ADF195'),
+ Bracket = Color3 .fromHex('#FFFFFF'),
+ Boolean = Color3 .fromHex('#B5CEA8'),
+ Error = Color3 .fromHex('#FF5050'),
+ Complete = Color3 .fromHex('#04385f'),
+ }
+ local SYMBOLS = {
+ Event = {},
+ Field = {},
+ Snippet = {},
+ Keyword = Table.GetDictionaryKeys(Settings.Keywords),
+ Variable = {},
+ Primitive = Table.GetDictionaryKeys(Settings.Primtives),
+ }
+ local FIELDS = {
+ struct = true,
+ event = true,
+ ['function'] = true,
+ }
+ local BRACKETS = {
+ OpenBrackets = true,
+ CloseBrackets = true,
+ OpenCurlyBrackets = true,
+ CloseCurlyBrackets = true,
+ OpenSquareBrackets = true,
+ CloseSquareBrackets = true,
+ }
+ local SCROLL_LINES = 2
+ local CURSOR_BLINK_RATE = 0.5
+ local Editor = {}
+ local Container = script.Widget
+ local EditorContainer = Container.Editor
+ local CompletionContainer = Container.Completion
+ local TextContainer = EditorContainer.Text
+ local LinesContainer = EditorContainer.Lines
+ local Input = TextContainer.Input
+ local Cursor = TextContainer.Cursor
+ local Display = TextContainer.Display
+ local Selection = TextContainer.Selection
+ local LineTemplate = LinesContainer.Line:Clone()
+ local SelectionTemplate = Selection.Line:Clone()
+ local CompletionTemplate = CompletionContainer.Option:Clone()
+ local TextSize = Input.TextSize
+ local TextHeight = (Input.TextSize + 3)
+ local SourceLexer = Lexer.new('Highlighting')
+ local SourceParser = Parser.new()
+ local Lines = State.new(0)
+ local Scroll = State.new(0)
+ local Errors = State.new({})
+ local CursorTimer = 0
+ local PreviousText = Input.Text
+
+ local function ScrollTowards(Direction)
+ local Value = Scroll:Get()
+ local Maximum = math.max(1, (Input.TextBounds.Y - EditorContainer.AbsoluteSize.Y) // TextHeight + SCROLL_LINES)
+
+ Scroll:Set(math.clamp(Value + (Direction * SCROLL_LINES), 0, Maximum))
+ end
+ local function WrapColor(Text, Color)
+ return `{Text}`
+ end
+ local function ClearChildrenWhichAre(Parent, Class)
+ for Index, Child in Parent:GetChildren()do
+ if Child:IsA(Class) then
+ Child:Destroy()
+ end
+ end
+ end
+ local function DoLexerPass()
+ local Source = Input.Text
+ local RichText = ''
+
+ SourceLexer:Initialize(Source)
+
+ local Keyword = 'none'
+ local IsField = false
+
+ while true do
+ local Success, Error, Token = pcall(function()
+ return nil, SourceLexer:GetNextToken()
+ end)
+
+ if not Success then
+ warn(`Lexer error: {Error}`)
+
+ break
+ end
+ if not Token then
+ break
+ end
+
+ local Type = Token.Type
+ local Value = Token.Value
+
+ if Type == 'Keyword' then
+ Keyword = Value
+
+ RichText ..= WrapColor(Value, COLORS.Keyword)
+ elseif Type == 'Primitive' then
+ RichText ..= WrapColor(Value, COLORS.Primitive)
+ elseif Type == 'Identifier' then
+ RichText ..= WrapColor(Value, IsField and COLORS.Text or COLORS.Identifier)
+ elseif Type == 'Array' or Type == 'Range' then
+ local Single = string.match(Value, '%[(%d+)%]') or string.match(Value, '%((%d+)%)')
+
+ if Single then
+ RichText ..= string.gsub(Value, Single, WrapColor(Single, COLORS.Number))
+
+ continue
+ end
+
+ local Lower = string.match(Value, '%[(%d+)') or string.match(Value, '%((%d+)')
+ local Upper = string.match(Value, '(%d+)%]') or string.match(Value, '(%d+)%)')
+
+ if Lower and Upper then
+ RichText ..= `{string.sub(Value, 1, 1)}{WrapColor(Lower, COLORS.Number)}..{WrapColor(Upper, COLORS.Number)}{string.sub(Value, #Value, #Value)}`
+
+ continue
+ end
+
+ RichText ..= Value
+ elseif BRACKETS[Type] then
+ if Type == 'CloseCurlyBrackets' then
+ IsField = false
+ end
+
+ RichText ..= WrapColor(Value, COLORS.Bracket)
+ elseif Type == 'Class' then
+ RichText ..= `({WrapColor(string.sub(Value, 2, #Value - 1), COLORS.Class)})`
+ elseif Type == 'String' then
+ RichText ..= WrapColor(Value, COLORS.String)
+ elseif Type == 'Boolean' then
+ RichText ..= WrapColor(Value, COLORS.Boolean)
+ elseif Type == 'Unknown' then
+ IsField = false
+
+ RichText ..= WrapColor(Value, COLORS.Error)
+ else
+ RichText ..= Value
+ end
+ if Type == 'Whitespace' then
+ continue
+ end
+
+ IsField = (Type == 'Comma' or Type == 'OpenCurlyBrackets') and FIELDS[Keyword]
+ end
+
+ Display.Text = RichText
+ end
+ local function OnErrorEmitted(Labels)
+ Errors:Set(Labels)
+ end
+ local function OnPreRender(DeltaTime)
+ if Input.CursorPosition == -1 then
+ return
+ end
+
+ CursorTimer += DeltaTime
+
+ if CursorTimer < CURSOR_BLINK_RATE then
+ return
+ end
+
+ CursorTimer -= CURSOR_BLINK_RATE
+
+ Cursor.Visible = not Cursor.Visible
+ end
+ local function OnLinesChanged(Value)
+ ClearChildrenWhichAre(LinesContainer, 'TextLabel')
+
+ for Index = 1, Value do
+ local Line = LineTemplate:Clone()
+
+ Line.Text = tostring(Index)
+ Line.Parent = LinesContainer
+ end
+ end
+ local function OnScrollChanged(Value)
+ EditorContainer.Position = UDim2 .fromOffset(EditorContainer.Position.X.Offset, TextHeight *
+-Value)
+ end
+ local function OnCursorPositionChanged()
+ local CursorPosition = Input.CursorPosition
+
+ if CursorPosition == -1 then
+ Cursor.Visible = false
+
+ return
+ end
+
+ local Text = string.sub(Input.Text, 1, CursorPosition - 1)
+ local Slices = string.split(Text, '\n')
+ local Line = Slices[#Slices] or ''
+ local GetTextBoundsParams = Instance.new('GetTextBoundsParams')
+
+ GetTextBoundsParams.Text = Line
+ GetTextBoundsParams.Size = TextSize
+ GetTextBoundsParams.Font = Input.FontFace
+
+ local TextBounds = TextService:GetTextBoundsAsync(GetTextBoundsParams)
+
+ Cursor.Size = UDim2 .fromOffset(2, TextSize)
+ Cursor.Position = UDim2 .fromOffset(TextBounds.X - 1, TextHeight * (#Slices - 1))
+ end
+ local function OnSelectionChanged()
+ ClearChildrenWhichAre(Selection, 'Frame')
+
+ local CursorPosition = Input.CursorPosition
+ local SelectionStart = Input.SelectionStart
+
+ if CursorPosition == -1 or SelectionStart == -1 then
+ return
+ end
+
+ local Start = math.min(CursorPosition, SelectionStart)
+ local Finish = math.max(CursorPosition, SelectionStart)
+ local Text = string.sub(Input.ContentText, 1, Finish - 1)
+ local Slices = string.split(Text, '\n')
+ local Offset = 0
+
+ for Index, Slice in Slices do
+ local First = Offset
+
+ Offset += (#Slice + 1)
+
+ local Line = SelectionTemplate:Clone()
+ local Fill = Line.Fill
+
+ if Offset < Start then
+ Fill.BackgroundTransparency = 1
+ Line.Parent = Selection
+
+ continue
+ end
+
+ local Substring = Slice
+
+ if First < Start then
+ Substring = string.sub(Slice, (Start - First), #Slice)
+ elseif Index == #Slices then
+ Substring = string.sub(Slice, 1, math.max(1, Finish - First))
+ end
+ if Substring == '' then
+ Substring = ' '
+ end
+
+ local SelectionBoundsParams = Instance.new('GetTextBoundsParams')
+
+ SelectionBoundsParams.Text = Substring
+ SelectionBoundsParams.Size = TextSize
+ SelectionBoundsParams.Font = Input.FontFace
+
+ local SelectionBounds = TextService:GetTextBoundsAsync(SelectionBoundsParams)
+
+ Fill.Size = UDim2 .new(0, SelectionBounds.X, 1, 0)
+
+ if Start > First then
+ local Prefix = string.sub(Slice, 1, (Start - First) - 1)
+
+ if Prefix ~= '' then
+ local OffsetBoundsParams = Instance.new('GetTextBoundsParams')
+
+ OffsetBoundsParams.Text = Prefix
+ OffsetBoundsParams.Size = TextSize
+ OffsetBoundsParams.Font = Input.FontFace
+
+ local OffsetBounds = TextService:GetTextBoundsAsync(OffsetBoundsParams)
+
+ Fill.Position = UDim2 .new(0, OffsetBounds.X, 0, 0)
+ end
+ end
+
+ Line.Parent = Selection
+ end
+ end
+ local function OnSourceChanged()
+ local Text = Input.Text
+ local Gain = math.sign(#Text - #PreviousText)
+ local NoReturnCarriage = string.gsub(Text, '\r', '')
+
+ if NoReturnCarriage ~= Text then
+ Input.Text = NoReturnCarriage
+
+ return
+ end
+
+ PreviousText = Text
+
+ local SourceLines = #string.split(Text, '\n')
+
+ if SourceLines ~= Lines:Get() then
+ Lines:Set(SourceLines)
+ end
+
+ local FinalText, FinalCursor = Text, Input.CursorPosition
+
+ for Index, Hook in StylingHooks do
+ FinalText, FinalCursor = Hook.OnSourceChanged(FinalText, FinalCursor, Gain)
+ end
+
+ if FinalText ~= Text then
+ Input.CursorPosition = -1
+ Input.Text = FinalText
+ Input.CursorPosition = FinalCursor
+
+ return
+ end
+
+ DoLexerPass()
+ end
+
+ function Editor.GetSource()
+ return Input.Text
+ end
+ function Editor.SetSource(Source)
+ Scroll:Set(0)
+
+ Input.Text = Source
+ end
+ function Editor.Initialize()
+ Selection.Line:Destroy()
+ LinesContainer.Line:Destroy()
+ CompletionContainer.Option:Destroy()
+
+ for _, Symbols in SYMBOLS do
+ table.sort(Symbols, function(a, b)
+ return #a < #b
+ end)
+ end
+
+ Error.OnEmit = OnErrorEmitted
+
+ Lines:OnChange(OnLinesChanged)
+ Scroll:OnChange(OnScrollChanged)
+ Input:GetPropertyChangedSignal('Text'):Connect(OnSourceChanged)
+ Input:GetPropertyChangedSignal('SelectionStart'):Connect(OnSelectionChanged)
+ Input:GetPropertyChangedSignal('CursorPosition'):Connect(OnSelectionChanged)
+ Input:GetPropertyChangedSignal('CursorPosition'):Connect(OnCursorPositionChanged)
+ Input.InputChanged:Connect(function(InputObject)
+ if InputObject.UserInputType == Enum.UserInputType.MouseWheel then
+ ScrollTowards(-InputObject.Position.Z)
+ end
+ end)
+ RunService.PreRender:Connect(OnPreRender)
+ end
+
+ return Editor
+ end
+ function __DARKLUA_BUNDLE_MODULES.j()
local Operators = {
Not = '~=',
Equals = '==',
@@ -1423,7 +2020,7 @@ do
Connection = Connection.new,
}
end
- function __DARKLUA_BUNDLE_MODULES.g()
+ function __DARKLUA_BUNDLE_MODULES.k()
local Builder = {}
function Builder.new(String, BaseIndentation)
@@ -1489,10 +2086,10 @@ do
return Builder
end
- function __DARKLUA_BUNDLE_MODULES.h()
- local Blocks = __DARKLUA_BUNDLE_MODULES.load('f')
- local Parser = __DARKLUA_BUNDLE_MODULES.load('e')
- local Builder = __DARKLUA_BUNDLE_MODULES.load('g')
+ function __DARKLUA_BUNDLE_MODULES.l()
+ local Blocks = __DARKLUA_BUNDLE_MODULES.load('j')
+ local Parser = __DARKLUA_BUNDLE_MODULES.load('g')
+ local Builder = __DARKLUA_BUNDLE_MODULES.load('k')
local SEND_BUFFER = 'SendBuffer'
local RECIEVE_BUFFER = 'RecieveBuffer'
local SEND_POSITION = 'SendOffset'
@@ -1556,7 +2153,10 @@ do
local IsVariableSize = (Range and Range.Max ~= Range.Min)
local function GenerateValidation(Block, Variable)
- if Range and AssertGenerator then
+ if not AssertGenerator then
+ return
+ end
+ if Range then
local Assert = AssertGenerator(Variable, Range.Min, Range.Max)
if not IsVariableSize and Assert.Exact then
@@ -1565,7 +2165,7 @@ do
Block:Line(Assert.Lower)
Block:Line(Assert.Upper)
end
- elseif Primitive == 'Instance' and AssertGenerator then
+ elseif Primitive == 'Instance' then
local Assert = AssertGenerator(Variable, Class)
if Value.Optional and Block == Write then
@@ -1810,6 +2410,21 @@ do
Generate = GeneratePrimitivePrefab(Types.Instance, Asserts.Instance),
}
end
+ do
+ Types.unknown = {
+ Read = function(Variable, Block)
+ Block:Line('RecieveInstanceCursor += 1')
+ Block:Line(`{Variable} = RecieveInstances[RecieveInstanceCursor]`)
+ end,
+ Write = function(Value, Block)
+ Block:Line(`table.insert(SendInstances, {Value})`)
+ end,
+ }
+ Primitives.unknown = {
+ Type = 'any',
+ Generate = GeneratePrimitivePrefab(Types.unknown),
+ }
+ end
return {
Types = Types,
@@ -1818,24 +2433,24 @@ do
Structures = Structures,
}
end
- function __DARKLUA_BUNDLE_MODULES.i()
+ function __DARKLUA_BUNDLE_MODULES.m()
return 'local Invocations = 0\r\n\r\nlocal SendOffset = 0\r\nlocal SendCursor = 0\r\nlocal SendBuffer = buffer.create(64)\r\nlocal SendInstances = {}\r\n\r\nlocal RecieveCursor = 0\r\nlocal RecieveBuffer = buffer.create(64)\r\n\r\nlocal RecieveInstances = {}\r\nlocal RecieveInstanceCursor = 0\r\n\r\ntype BufferSave = {Cursor: number, Buffer: buffer, Instances: {Instance}}\r\n\r\nlocal function Read(Bytes: number)\r\n local Offset = RecieveCursor\r\n RecieveCursor += Bytes\r\n return Offset\r\nend\r\n\r\nlocal function Save(): BufferSave\r\n return {\r\n Cursor = SendCursor,\r\n Buffer = SendBuffer,\r\n Instances = SendInstances\r\n }\r\nend\r\n\r\nlocal function Load(Save: BufferSave?)\r\n if Save then\r\n SendCursor = Save.Cursor\r\n SendOffset = Save.Cursor\r\n SendBuffer = Save.Buffer\r\n SendInstances = Save.Instances\r\n return\r\n end\r\n\r\n SendCursor = 0\r\n SendOffset = 0\r\n SendBuffer = buffer.create(64)\r\n SendInstances = {}\r\nend\r\n\r\nlocal function Invoke()\r\n if Invocations == 255 then\r\n Invocations = 0\r\n end\r\n\r\n local Invocation = Invocations\r\n Invocations += 1\r\n return Invocation\r\nend\r\n\r\nlocal function Allocate(Bytes: number)\r\n local Len = buffer.len(SendBuffer)\r\n\r\n local Size = Len\r\n local InUse = (SendCursor + Bytes)\r\n \r\n if InUse > Size then\r\n --> Avoid resizing the buffer for every write\r\n while InUse > Size do\r\n Size *= 1.5\r\n end\r\n\r\n local Buffer = buffer.create(Size)\r\n buffer.copy(Buffer, 0, SendBuffer, 0, Len)\r\n SendBuffer = Buffer\r\n end\r\n\r\n SendOffset = SendCursor\r\n SendCursor += Bytes\r\n \r\n return SendOffset\r\nend\r\n\r\nlocal Types = {}\r\nlocal Calls = table.create(256)\r\n\r\nlocal Events: any = {\r\n Reliable = table.create(256),\r\n Unreliable = table.create(256)\r\n}\r\n\r\nlocal Queue: any = {\r\n Reliable = table.create(256),\r\n Unreliable = table.create(256)\r\n}\r\n\r\n'
end
- function __DARKLUA_BUNDLE_MODULES.j()
+ function __DARKLUA_BUNDLE_MODULES.n()
return 'local ReplicatedStorage = game:GetService("ReplicatedStorage")\r\nlocal RunService = game:GetService("RunService")\r\n\r\nif not RunService:IsClient() then\r\n error("Client network module can only be required from the client.")\r\nend\r\n\r\nlocal Reliable: RemoteEvent = ReplicatedStorage:WaitForChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent\r\nlocal Unreliable: UnreliableRemoteEvent = ReplicatedStorage:WaitForChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent\r\n\r\n-- SPLIT --\r\nlocal function StepReplication()\r\n if SendCursor <= 0 then\r\n return\r\n end\r\n\r\n local Buffer = buffer.create(SendCursor)\r\n buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)\r\n Reliable:FireServer(Buffer, SendInstances)\r\n\r\n SendCursor = 0\r\n SendOffset = 0\r\n buffer.fill(SendBuffer, 0, 0)\r\n table.clear(SendInstances)\r\nend\r\n'
end
- function __DARKLUA_BUNDLE_MODULES.k()
+ function __DARKLUA_BUNDLE_MODULES.o()
return 'local Players = game:GetService("Players")\r\nlocal ReplicatedStorage = game:GetService("ReplicatedStorage")\r\nlocal RunService = game:GetService("RunService")\r\n\r\nif not RunService:IsServer() then\r\n error("Server network module can only be required from the server.")\r\nend\r\n\r\nlocal Reliable: RemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent\r\nif not Reliable then\r\n local RemoteEvent = Instance.new("RemoteEvent")\r\n RemoteEvent.Name = "BLINK_RELIABLE_REMOTE"\r\n RemoteEvent.Parent = ReplicatedStorage\r\n Reliable = RemoteEvent\r\nend\r\n\r\nlocal Unreliable: UnreliableRemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent\r\nif not Unreliable then\r\n local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent")\r\n UnreliableRemoteEvent.Name = "BLINK_UNRELIABLE_REMOTE"\r\n UnreliableRemoteEvent.Parent = ReplicatedStorage\r\n Unreliable = UnreliableRemoteEvent\r\nend\r\n\r\n-- SPLIT --\r\nlocal PlayersMap: {[Player]: BufferSave} = {}\r\n\r\nPlayers.PlayerRemoving:Connect(function(Player)\r\n PlayersMap[Player] = nil\r\nend)\r\n\r\nlocal function StepReplication()\r\n for Player, Send in PlayersMap do\r\n if Send.Cursor <= 0 then\r\n continue\r\n end\r\n\r\n local Buffer = buffer.create(Send.Cursor)\r\n buffer.copy(Buffer, 0, Send.Buffer, 0, Send.Cursor)\r\n Reliable:FireClient(Player, Buffer, Send.Instances)\r\n\r\n Send.Cursor = 0\r\n buffer.fill(Send.Buffer, 0, 0)\r\n table.clear(Send.Instances)\r\n end\r\nend\r\n'
end
- function __DARKLUA_BUNDLE_MODULES.l()
- local Blocks = __DARKLUA_BUNDLE_MODULES.load('f')
- local Parser = __DARKLUA_BUNDLE_MODULES.load('e')
- local Builder = __DARKLUA_BUNDLE_MODULES.load('g')
- local Prefabs = __DARKLUA_BUNDLE_MODULES.load('h')
+ function __DARKLUA_BUNDLE_MODULES.p()
+ local Blocks = __DARKLUA_BUNDLE_MODULES.load('j')
+ local Parser = __DARKLUA_BUNDLE_MODULES.load('g')
+ local Builder = __DARKLUA_BUNDLE_MODULES.load('k')
+ local Prefabs = __DARKLUA_BUNDLE_MODULES.load('l')
local Sources = {
- Base = __DARKLUA_BUNDLE_MODULES.load('i'),
- Client = string.split(__DARKLUA_BUNDLE_MODULES.load('j'), '-- SPLIT --'),
- Server = string.split(__DARKLUA_BUNDLE_MODULES.load('k'), '-- SPLIT --'),
+ Base = __DARKLUA_BUNDLE_MODULES.load('m'),
+ Client = string.split(__DARKLUA_BUNDLE_MODULES.load('n'), '-- SPLIT --'),
+ Server = string.split(__DARKLUA_BUNDLE_MODULES.load('o'), '-- SPLIT --'),
}
local DIRECTIVES =
[[--!strict
@@ -1844,7 +2459,7 @@ do
--!nolint LocalShadow
--#selene: allow(shadowing)
]]
- local VERSION_HEADER = `-- File generated by Blink v{'0.6.4' or '0.0.0'} (https://github.com/1Axen/Blink)\n-- This file is not meant to be edited\n\n`
+ local VERSION_HEADER = `-- File generated by Blink v{'0.7.0' or '0.0.0'} (https://github.com/1Axen/Blink)\n-- This file is not meant to be edited\n\n`
local EVENT_BODY = 'RecieveCursor = 0\r\nRecieveBuffer = Buffer\r\nRecieveInstances = Instances\r\nRecieveInstanceCursor = 0\r\nlocal Size = buffer.len(RecieveBuffer)'
local RELIABLE_BODY = {
Header =
@@ -2252,8 +2867,15 @@ do
local Value = Declaration.Value
local Optional = Value.Optional
local Variable = Variable or 'Value'
- local IsInstance = (Declaration.Type == 'TypeDeclaration' and (((Value).Primitive)) == 'Instance')
+ local IsInstance, IsUnknown
+
+ if Declaration.Type == 'TypeDeclaration' then
+ local Primitive = (Value).Primitive
+ IsUnknown = (Primitive == 'unknown')
+ IsInstance = (Primitive == 'Instance')
+ Optional = if IsUnknown then true else Optional
+ end
if not true then
Read:Comment(`{Variable}: {Value.Identifier}`)
Write:Comment(`{Variable}: {Value.Identifier}`)
@@ -2434,19 +3056,29 @@ do
end
end
- return function(FileContext, AbstractSyntaxTree, UserOptions)
- local Imports = Builder.new()
+ local Generator = {}
+ function Generator.Reset()
Events = Builder.new()
UserTypes = Builder.new()
LuauTypes = Builder.new()
Return = Builder.new()
- Context = FileContext
- Options = UserOptions
Channels.Reliable.Count = 0
Channels.Unreliable.Count = 0
Channels.Reliable.Listening = false
Channels.Unreliable.Listening = false
+ end
+ function Generator.Generate(
+ FileContext,
+ AbstractSyntaxTree,
+ UserOptions
+ )
+ local Imports = Builder.new()
+
+ Generator.Reset()
+
+ Context = FileContext
+ Options = UserOptions
local Signal = Context == 'Client' and 'OnClientEvent' or 'OnServerEvent'
local Arguments = (Context == 'Server' and 'Player: Player, ' or '') .. 'Buffer: buffer, Instances: {Instance}'
@@ -2502,66 +3134,62 @@ do
return DIRECTIVES .. VERSION_HEADER .. (if not true then string.format(DEBUUG_GLOBALS, string.lower(Context), string.lower(Context))else'') .. Source[1] .. Imports.Dump() .. Sources.Base .. Events.Dump() .. '\n' .. LuauTypes.Dump() .. '\n' .. UserTypes.Dump() .. Source[2] .. '\n' .. Replication.Dump() .. '\n' .. Reliables:Unwrap() .. '\n\n' .. Unreliables:Unwrap() .. '\n' .. `\nreturn \{\n{Return.Dump()}}`
end
+ function Generator.GenerateTypeDefinitions(
+ FileContext,
+ AbstractSyntaxTree,
+ UserOptions
+ )
+ Generator.Reset()
+
+ Context = FileContext
+ Options = UserOptions
+
+ Generators.AbstractSyntaxTree(AbstractSyntaxTree.Value)
+
+ return LuauTypes.Dump() .. `\nreturn true`
+ end
+
+ return Generator
end
end
local ServerStorage = game:GetService('ServerStorage')
local UserInputService = game:GetService('UserInputService')
+local ContextActionService = game:GetService('ContextActionService')
local TweenService = game:GetService('TweenService')
local TextService = game:GetService('TextService')
local RunService = game:GetService('RunService')
local Selection = game:GetService('Selection')
local State = __DARKLUA_BUNDLE_MODULES.load('a')
-local Lexer = __DARKLUA_BUNDLE_MODULES.load('c')
-local Parser = __DARKLUA_BUNDLE_MODULES.load('e')
-local Generator = __DARKLUA_BUNDLE_MODULES.load('l')
-local EDITOR_COLORS = {
- Text = Color3 .fromHex('#FFFFFF'),
- Keyword = Color3 .fromHex('#6796E6'),
- Primitive = Color3 .fromHex('#4EC9B0'),
- Identifier = Color3 .fromHex('#9CDCFE'),
- Class = Color3 .fromHex('#B5CEA8'),
- Number = Color3 .fromHex('#B5CEA8'),
- String = Color3 .fromHex('#ADF195'),
- Bracket = Color3 .fromHex('#FFFFFF'),
- Boolean = Color3 .fromHex('#B5CEA8'),
- Error = Color3 .fromHex('#FF5050'),
-}
+local Editor = __DARKLUA_BUNDLE_MODULES.load('i')
+local Parser = __DARKLUA_BUNDLE_MODULES.load('g')
+local Generator = __DARKLUA_BUNDLE_MODULES.load('p')
local FILES_FOLDER = 'BLINK_CONFIGURATION_FILES'
local TEMPLATE_FILE = {
Name = 'Template',
- Source = 'type Example = u8\r\nevent MyEvent = {\r\n From = Server,\r\n Type = Reliable,\r\n Call = SingleSync,\r\n Data = Example\r\n}',
+ Source = table.concat({
+ 'type Example = u8',
+ 'event MyEvent = {',
+ '\tFrom = Server,',
+ '\tType = Reliable,',
+ '\tCall = SingleSync,',
+ '\tData = Example',
+ '}',
+ }, '\n'),
}
local ERROR_WIDGET = DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Float, false, true, 500, 240, 300, 240)
local EDITOR_WIDGET = DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Left, false, false, 360, 400, 300, 400)
local SAVE_COLOR = Color3 .fromRGB(0, 100, 0)
local BUTTON_COLOR = Color3 .fromRGB(30, 30, 30)
local EXPAND_TWEEN = TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
-local SCROLL_LINES = 2
-local CURSOR_BLINK_RATE = 0.5
-local FIELDS_TYPES = {
- struct = true,
- event = true,
- ['function'] = true,
-}
-local BRACKETS_TYPES = {
- OpenBrackets = true,
- CloseBrackets = true,
- OpenCurlyBrackets = true,
- CloseCurlyBrackets = true,
- OpenSquareBrackets = true,
- CloseSquareBrackets = true,
-}
local Toolbar = plugin:CreateToolbar('Blink Suite')
local EditorButton = Toolbar:CreateButton('Editor', 'Opens the configuration editor', 'rbxassetid://16468561002')
EditorButton.ClickableWhenViewportHidden = true
local Template = script.Widget
-local LineTemplate = Template.Editor.Lines.Line:Clone()
local FileTemplate = Template.Side.Content.Top.Files.File:Clone()
-Template.Editor.Lines.Line:Destroy()
Template.Side.Content.Top.Files.File:Destroy()
local ErrorWidget = plugin:CreateDockWidgetPluginGui('Generation Error', ERROR_WIDGET)
@@ -2581,13 +3209,12 @@ EditorWidget.Name = 'Blink Editor'
EditorWidget.Title = 'Configuration Editor'
EditorWidget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
-local EditorInterface = Template:Clone()
+local EditorInterface = script.Widget
EditorInterface.Size = UDim2 .fromScale(1, 1)
EditorInterface.Parent = EditorWidget
local Side = EditorInterface.Side
-local Editor = EditorInterface.Editor
local Overlay = EditorInterface.Overlay
local Content = Side.Content
local Expand = Side.Expand
@@ -2598,18 +3225,13 @@ local Search = Top.Title.Search.Input
local Prompt = Bottom.Save
local Buttons = Bottom.Buttons
local GeneratePrompt = Bottom.Generate
-local GenerateButtons = (GeneratePrompt:FindFirstChild('Buttons'))
-local Hint = (GeneratePrompt:FindFirstChild('Hint'))
-local Generate = (GenerateButtons:FindFirstChild('Generate'))
-local CancelGenerate = (GenerateButtons:FindFirstChild('Cancel'))
-local Input = (Prompt:FindFirstChild('Input'))
+local GenerateButtons = GeneratePrompt.Buttons
+local Hint = GeneratePrompt.Hint
+local Generate = GenerateButtons.Generate
+local CancelGenerate = GenerateButtons.Cancel
+local Input = Prompt.Input
local Save = Buttons.Save
local Cancel = Buttons.Cancel
-local TextInput = Editor.Text.Input
-local TextCursor = Editor.Text.Cursor
-local TextDisplay = Editor.Text.Display
-local NumberLines = Editor.Lines
-local TextLineHeight = (TextInput.TextSize + 3)
local Tweens = {
Editor = {
Expand = {
@@ -2628,16 +3250,12 @@ local Tweens = {
},
},
}
-local SourceLexer = Lexer.new('Highlighting')
local Saving = false
-local Scroll = State.new(0)
local Expanded = State.new(false)
local Selected = State.new(nil)
local Generating = State.new(nil)
local WasOpened = false
-local Lines = 0
local Editing
-local CursorTimer = 0
local function ShowError(Error)
local Start = string.find(Error, '{Text}`
-end
-local function GetEditorSource()
- return TextInput.ContentText
-end
-local function DoLexerPass()
- local Source = GetEditorSource()
- local Display = ''
-
- SourceLexer:Initialize(Source)
-
- local Keyword = 'none'
- local IsField = false
-
- while true do
- local Success, Error, Token = pcall(function()
- return nil, SourceLexer:GetNextToken()
- end)
-
- if not Success then
- warn(`Lexer error: {Error}`)
-
- break
- end
- if not Token then
- break
- end
-
- local Type = Token.Type
- local Value = Token.Value
-
- if Type == 'Keyword' then
- Keyword = Value
-
- Display ..= WrapColor(Value, EDITOR_COLORS.Keyword)
- elseif Type == 'Primitive' then
- Display ..= WrapColor(Value, EDITOR_COLORS.Primitive)
- elseif Type == 'Identifier' then
- Display ..= WrapColor(Value, IsField and EDITOR_COLORS.Text or EDITOR_COLORS.Identifier)
- elseif Type == 'Array' or Type == 'Range' then
- local Single = string.match(Value, '%[(%d+)%]') or string.match(Value, '%((%d+)%)')
-
- if Single then
- Display ..= string.gsub(Value, Single, WrapColor(Single, EDITOR_COLORS.Number))
-
- continue
- end
-
- local Lower = string.match(Value, '%[(%d+)') or string.match(Value, '%((%d+)')
- local Upper = string.match(Value, '(%d+)%]') or string.match(Value, '(%d+)%)')
-
- if Lower and Upper then
- Display ..= `{string.sub(Value, 1, 1)}{WrapColor(Lower, EDITOR_COLORS.Number)}..{WrapColor(Upper, EDITOR_COLORS.Number)}{string.sub(Value, #Value, #Value)}`
-
- continue
- end
-
- Display ..= Value
- elseif BRACKETS_TYPES[Type] then
- if Type == 'CloseCurlyBrackets' then
- IsField = false
- end
-
- Display ..= WrapColor(Value, EDITOR_COLORS.Bracket)
- elseif Type == 'Class' then
- Display ..= `({WrapColor(string.sub(Value, 2, #Value - 1), EDITOR_COLORS.Class)})`
- elseif Type == 'String' then
- Display ..= WrapColor(Value, EDITOR_COLORS.String)
- elseif Type == 'Boolean' then
- Display ..= WrapColor(Value, EDITOR_COLORS.Boolean)
- elseif Type == 'Unknown' then
- IsField = false
-
- Display ..= WrapColor(Value, EDITOR_COLORS.Error)
- else
- Display ..= Value
- end
- if Type == 'Whitespace' then
- continue
- end
-
- IsField = (Type == 'Comma' or Type == 'OpenCurlyBrackets') and FIELDS_TYPES[Keyword]
- end
-
- TextDisplay.Text = Display
-end
-local function ScrollEditor(Direction)
- local Value = Scroll:Get()
- local Maximum = math.max(1, (TextInput.TextBounds.Y - Editor.AbsoluteSize.Y) // TextLineHeight + SCROLL_LINES)
-
- Scroll:Set(math.clamp(Value + (Direction * SCROLL_LINES), 0, Maximum))
-end
-local function OnScroll(Value)
- Editor.Position = UDim2 .fromOffset(Editor.Position.X.Offset, TextLineHeight *
--Value)
-end
local function OnSearch(PressedEnter)
if not PressedEnter then
return
@@ -2906,54 +3428,6 @@ local function OnSearch(PressedEnter)
Frame.Visible = (string.find(Frame.Name, Query) ~= nil)
end
end
-local function OnCursorMoved()
- local CursorPosition = TextInput.CursorPosition
-
- if CursorPosition == -1 then
- TextCursor.Visible = false
-
- return
- end
-
- local Size = TextInput.TextSize
- local Text = string.sub(TextInput.ContentText, 1, CursorPosition - 1)
- local Slices = string.split(Text, '\n')
- local NewLines = math.max(#Slices, 1)
- local Line = Slices[#Slices] or ''
- local GetTextBoundsParams = Instance.new('GetTextBoundsParams')
-
- GetTextBoundsParams.Text = Line
- GetTextBoundsParams.Size = Size
- GetTextBoundsParams.Font = TextInput.FontFace
-
- local TextBounds = TextService:GetTextBoundsAsync(GetTextBoundsParams)
-
- TextCursor.Size = UDim2 .fromOffset(2, Size)
- TextCursor.Position = UDim2 .fromOffset(TextBounds.X - 1, TextLineHeight * (NewLines - 1))
-end
-local function OnSourceChanged()
- local ContentText = TextInput.ContentText
-
- ContentText = string.gsub(ContentText, '\r', '')
- TextInput.Text = ContentText
-
- local ContentLines = #string.split(ContentText, '\n')
-
- if ContentLines ~= Lines then
- Lines = ContentLines
-
- ClearChildrenWhichAre(NumberLines, 'TextLabel')
-
- for Index = 1, Lines do
- local Line = LineTemplate:Clone()
-
- Line.Text = tostring(Index)
- Line.Parent = NumberLines
- end
- end
-
- DoLexerPass()
-end
local function OnSaveCompleted()
Saving = false
Prompt.Visible = false
@@ -2968,7 +3442,7 @@ local function OnSaveActivated()
return
end
- SaveFile(Name, GetEditorSource())
+ SaveFile(Name, Editor.GetSource())
LoadFiles()
OnSaveCompleted()
@@ -2994,23 +3468,8 @@ end
local function OnEditorButtonClicked()
EditorWidget.Enabled = not EditorWidget.Enabled
end
-local function OnPreRender(DeltaTime)
- if TextInput.CursorPosition == -1 then
- return
- end
- CursorTimer += DeltaTime
-
- if CursorTimer < CURSOR_BLINK_RATE then
- return
- end
-
- CursorTimer -= CURSOR_BLINK_RATE
-
- TextCursor.Visible = not TextCursor.Visible
-end
-
-Scroll:OnChange(OnScroll)
+Editor.Initialize()
Expanded:OnChange(function(Value)
PlayTweens(Value and Tweens.Editor.Expand or Tweens.Editor.Retract)
end)
@@ -3051,14 +3510,3 @@ Save.Activated:Connect(OnSaveActivated)
Cancel.Activated:Connect(OnSaveCompleted)
Expand.Activated:Connect(OnExpandActivated)
EditorButton.Click:Connect(OnEditorButtonClicked)
-TextInput:GetPropertyChangedSignal('Text'):Connect(OnSourceChanged)
-TextInput:GetPropertyChangedSignal('CursorPosition'):Connect(OnCursorMoved)
-TextInput.InputChanged:Connect(function(InputObject)
- if not Editing then
- return
- end
- if InputObject.UserInputType == Enum.UserInputType.MouseWheel then
- ScrollEditor(-InputObject.Position.Z)
- end
-end)
-RunService.PreRender:Connect(OnPreRender)
diff --git a/plugin/src/Editor/Styling/AutoIndent.luau b/plugin/src/Editor/Styling/AutoIndent.luau
new file mode 100644
index 0000000..caf6933
--- /dev/null
+++ b/plugin/src/Editor/Styling/AutoIndent.luau
@@ -0,0 +1,76 @@
+local Hook = {}
+
+local IndentKeywords = {
+ "{\n"
+}
+
+local function ShouldAutoIndent(Text: string, Cursor: number): boolean
+ for Index, Keyword in IndentKeywords do
+ local Position = (Cursor - #Keyword)
+ if Position <= 0 then
+ continue
+ end
+
+ local Previous = string.sub(Text, Position, Cursor - 1)
+ if Previous == Keyword then
+ return true
+ end
+ end
+
+ return false
+end
+
+local function GetLineIndentation(Line: string): number
+ return #(string.match(Line, "^\t*") :: string)
+end
+
+local function GetCurrentLine(Text: string, Cursor: number): (number, {string})
+ local Line = 0
+ local Position = 0
+ local Slices = string.split(Text, "\n")
+
+ for Index, Slice in Slices do
+ Position += (#Slice + 1)
+ if Cursor <= Position then
+ Line = Index
+ break
+ end
+ end
+
+ return Line, Slices
+end
+
+function Hook.OnSourceChanged(Text: string, Cursor: number, Gain: number): (string, number)
+ if Gain ~= 1 then
+ return Text, Cursor
+ end
+
+ local CanIndent = false
+ local AdditionalIndent = 0
+ local Line, Lines = GetCurrentLine(Text, Cursor)
+
+ local Current = Lines[Line]
+ local Previous = Lines[Line - 1]
+ local JustReached = (Previous and Current == "")
+
+ if ShouldAutoIndent(Text, Cursor) then
+ CanIndent = true
+ AdditionalIndent = 1
+ elseif JustReached then
+ if GetLineIndentation(Previous) > 0 then
+ CanIndent = true
+ end
+ end
+
+ if not CanIndent then
+ return Text, Cursor
+ end
+
+ --> Update text and cursor
+ local Indentation = GetLineIndentation(Previous) + AdditionalIndent
+ Text = string.sub(Text, 1, Cursor - 1) .. string.rep("\t", Indentation) .. string.sub(Text, Cursor)
+
+ return Text, Cursor + Indentation
+end
+
+return Hook
\ No newline at end of file
diff --git a/plugin/src/Editor/init.luau b/plugin/src/Editor/init.luau
new file mode 100644
index 0000000..3eaa34e
--- /dev/null
+++ b/plugin/src/Editor/init.luau
@@ -0,0 +1,548 @@
+--!strict
+
+-- ******************************* --
+-- AX3NX / AXEN --
+-- ******************************* --
+
+---- Services ----
+
+local TextService = game:GetService("TextService")
+local RunService = game:GetService("RunService")
+
+---- Imports ----
+
+local Table = require("../Table")
+local State = require("../State")
+
+local Lexer = require("../../../src/Lexer")
+local Parser = require("../../../src/Parser")
+local Settings = require("../../../src/Settings")
+
+local Error = require("../../../src/Modules/Error")
+
+local StylingHooks: {{OnSourceChanged: (Text: string, Cursor: number, Gain: number) -> (string, number)}} = {
+ require("./Styling/AutoIndent"),
+}
+
+---- Settings ----
+
+local ICONS = {
+ Event = "rbxassetid://16506730516",
+ Field = "rbxassetid://16506725096",
+ Snippet = "rbxassetid://16506712161",
+ Keyword = "rbxassetid://16506695241",
+ Variable = "rbxassetid://16506719167",
+ Primitive = "rbxassetid://16506695241"
+}
+
+local COLORS = {
+ Text = Color3.fromHex("#FFFFFF"),
+ Keyword = Color3.fromHex("#6796E6"),
+ Primitive = Color3.fromHex("#4EC9B0"),
+ Identifier = Color3.fromHex("#9CDCFE"),
+
+ Class = Color3.fromHex("#B5CEA8"),
+ Number = Color3.fromHex("#B5CEA8"),
+ String = Color3.fromHex("#ADF195"),
+ Bracket = Color3.fromHex("#FFFFFF"),
+ Boolean = Color3.fromHex("#B5CEA8"),
+
+ Error = Color3.fromHex("#FF5050"),
+
+ Complete = Color3.fromHex("#04385f")
+}
+
+local SYMBOLS: {[string]: {string}} = {
+ Event = {},
+ Field = {},
+ Snippet = {},
+ Keyword = Table.GetDictionaryKeys(Settings.Keywords),
+ Variable = {},
+ Primitive = Table.GetDictionaryKeys(Settings.Primtives)
+}
+
+local FIELDS = {
+ struct = true,
+ event = true,
+ ["function"] = true,
+}
+
+local BRACKETS = {
+ OpenBrackets = true,
+ CloseBrackets = true,
+ OpenCurlyBrackets = true,
+ CloseCurlyBrackets = true,
+ OpenSquareBrackets = true,
+ CloseSquareBrackets = true
+}
+
+local SCROLL_LINES = 2
+local CURSOR_BLINK_RATE = 0.5
+
+---- Constants ----
+
+local Editor = {}
+
+--> Interface Instances
+local Container: typeof(script.Parent.Widget) = script.Widget
+local EditorContainer = Container.Editor
+local CompletionContainer = Container.Completion
+
+local TextContainer = EditorContainer.Text
+local LinesContainer = EditorContainer.Lines
+
+local Input = TextContainer.Input
+local Cursor = TextContainer.Cursor
+local Display = TextContainer.Display
+local Selection = TextContainer.Selection
+
+local LineTemplate = LinesContainer.Line:Clone()
+local SelectionTemplate = Selection.Line:Clone()
+local CompletionTemplate = CompletionContainer.Option:Clone()
+
+local TextSize = Input.TextSize
+local TextHeight = (Input.TextSize + 3)
+
+--> Objects
+local SourceLexer = Lexer.new("Highlighting")
+local SourceParser = Parser.new()
+
+---- Variables ----
+
+local Lines = State.new(0)
+local Scroll = State.new(0)
+local Errors: State.Class<{Error.Label}> = State.new({})
+
+local CursorTimer = 0
+local PreviousText = Input.Text
+
+---- Private Functions ----
+
+local function ScrollTowards(Direction: number)
+ local Value = Scroll:Get()
+ local Maximum = math.max(1, (Input.TextBounds.Y - EditorContainer.AbsoluteSize.Y) // TextHeight + SCROLL_LINES)
+ Scroll:Set(math.clamp(Value + (Direction * SCROLL_LINES), 0, Maximum))
+end
+
+local function WrapColor(Text: string, Color: Color3): string
+ return `{Text}`
+end
+
+local function ClearChildrenWhichAre(Parent: Instance, Class: string)
+ for Index, Child in Parent:GetChildren() do
+ if Child:IsA(Class) then
+ Child:Destroy()
+ end
+ end
+end
+
+local function DoLexerPass()
+ local Source = Input.Text
+ local RichText = ""
+
+ --> Initiialize lexer
+ SourceLexer:Initialize(Source)
+
+ --> Highlight state
+ local Keyword = "none"
+ local IsField = false
+ --local IsAssigning = false
+
+ while true do
+ local Success, Error, Token = pcall(function()
+ return nil, SourceLexer:GetNextToken()
+ end)
+
+ if not Success then
+ warn(`Lexer error: {Error}`)
+ break
+ end
+
+ if not Token then
+ break
+ end
+
+ local Type: Lexer.Types = Token.Type
+ local Value = Token.Value
+
+ if Type == "Keyword" then
+ Keyword = Value
+ RichText ..= WrapColor(Value, COLORS.Keyword)
+ elseif Type == "Primitive" then
+ RichText ..= WrapColor(Value, COLORS.Primitive)
+ elseif Type == "Identifier" then
+ RichText ..= WrapColor(Value, IsField and COLORS.Text or COLORS.Identifier)
+ elseif Type == "Array" or Type == "Range" then
+ --> Exact size array/range
+ local Single = string.match(Value, "%[(%d+)%]") or string.match(Value, "%((%d+)%)")
+ if Single then
+ RichText ..= string.gsub(Value, Single, WrapColor(Single, COLORS.Number))
+ continue
+ end
+
+ --> Variable size array/range
+ local Lower = string.match(Value, "%[(%d+)") or string.match(Value, "%((%d+)")
+ local Upper = string.match(Value, "(%d+)%]") or string.match(Value, "(%d+)%)")
+ if Lower and Upper then
+ RichText ..= `{string.sub(Value, 1, 1)}{WrapColor(Lower, COLORS.Number)}..{WrapColor(Upper, COLORS.Number)}{string.sub(Value, #Value, #Value)}`
+ continue
+ end
+
+ RichText ..= Value
+ elseif BRACKETS[Type] then
+ if Type == "CloseCurlyBrackets" then
+ IsField = false
+ end
+
+ RichText ..= WrapColor(Value, COLORS.Bracket)
+ elseif Type == "Class" then
+ RichText ..= `({WrapColor(string.sub(Value, 2, #Value - 1), COLORS.Class)})`
+ elseif Type == "String" then
+ RichText ..= WrapColor(Value, COLORS.String)
+ elseif Type == "Boolean" then
+ RichText ..= WrapColor(Value, COLORS.Boolean)
+ elseif Type == "Unknown" then
+ --> Reset all state
+ IsField = false
+ RichText ..= WrapColor(Value, COLORS.Error)
+ else
+ RichText ..= Value
+ end
+
+ if Type == "Whitespace" then
+ continue
+ end
+
+ IsField = (Type == "Comma" or Type == "OpenCurlyBrackets") and FIELDS[Keyword]
+ --IsAssigning = (Type == "Assign")
+ end
+
+ Display.Text = RichText
+end
+
+--[[local function DoCompletionPass()
+ local Text = TextInput.ContentText
+ local Lines = string.split(Text, "\n")
+ local CursorPosition = TextInput.CursorPosition
+
+ if CursorPosition == -1 then
+ return
+ end
+
+ local Offset = 0
+ for _, Line in Lines do
+ local Start = Offset
+ Offset += (#Line + 1)
+ if Offset < CursorPosition then
+ continue
+ end
+
+ local Substring = string.sub(Line, 1, math.max(1, CursorPosition - Start))
+ local Words = string.split(Substring, " ")
+
+ local Size = #Words
+ local Token = Words[Size]
+ local Previous = Words[Size - 1]
+
+ print(`Token: {Token}`)
+
+ --> Match categories
+ local Items: {Prediction} = {}
+ for Category, Symbols in EDITOR_AUTOCOMPLETE do
+ if Token == "" then
+ break
+ end
+
+ local Result: string?;
+ for _, Symbol in Symbols do
+ if Token == Symbol then
+ break
+ end
+
+ if string.find(Symbol, Token, 1, true) then
+ Result = Symbol
+ break
+ end
+ end
+
+ --> Keywords should not follow assignment
+ if Category == "Keyword" and Previous == "=" then
+ continue
+ end
+
+ --> Primitives should only follow assignment
+ if Category == "Primitive" and Previous ~= "=" then
+ continue
+ end
+
+ if Result then
+ table.insert(Items, {
+ Icon = EDITOR_ICONS[Category],
+ Prediction = Result
+ })
+ end
+
+ print(`{Category}: {Result}`)
+ end
+
+ Predictions:Set({
+ Item = math.min(
+ Predictions:Get().Item,
+ math.max(1, #Items)
+ ),
+ Predictions = Items
+ })
+
+ break
+ end
+end]]
+
+---- Event Functions -----
+
+local function OnErrorEmitted(Labels)
+ Errors:Set(Labels)
+end
+
+local function OnPreRender(DeltaTime: number)
+ if Input.CursorPosition == -1 then
+ return
+ end
+
+ CursorTimer += DeltaTime
+ if CursorTimer < CURSOR_BLINK_RATE then
+ return
+ end
+
+ CursorTimer -= CURSOR_BLINK_RATE
+ Cursor.Visible = not Cursor.Visible
+end
+
+--> State functions
+local function OnLinesChanged(Value: number)
+ ClearChildrenWhichAre(LinesContainer, "TextLabel")
+ for Index = 1, Value do
+ local Line = LineTemplate:Clone()
+ Line.Text = tostring(Index)
+ Line.Parent = LinesContainer
+ end
+end
+
+local function OnScrollChanged(Value: number)
+ EditorContainer.Position = UDim2.fromOffset(EditorContainer.Position.X.Offset, TextHeight * -Value)
+end
+
+--[[local function OnPredictionsChanged()
+ local Value = Predictions:Get()
+ if #Value.Predictions == 0 then
+ Completion.Visible = false
+ return
+ end
+
+ Completion.Visible = true
+ ClearChildrenWhichAre(Completion, "Frame")
+
+ for Index, Item in Value.Predictions do
+ local Frame = CompletionItem:Clone()
+ Frame.Icon.Image = Item.Icon
+ Frame.Text.Text = Item.Prediction
+
+ if Index == Value.Item then
+ Frame.BackgroundColor3 = EDITOR_COLORS.Complete
+ end
+
+ Frame.Parent = Completion
+ end
+end]]
+
+--> RBXScriptSignal functions
+local function OnCursorPositionChanged()
+ local CursorPosition = Input.CursorPosition
+ if CursorPosition == -1 then
+ Cursor.Visible = false
+ return
+ end
+
+ local Text = string.sub(Input.Text, 1, CursorPosition - 1)
+ local Slices = string.split(Text, "\n")
+
+ --> Calculate current end line position
+ local Line = Slices[#Slices] or ""
+ local GetTextBoundsParams = Instance.new("GetTextBoundsParams")
+ GetTextBoundsParams.Text = Line
+ GetTextBoundsParams.Size = TextSize
+ GetTextBoundsParams.Font = Input.FontFace
+ local TextBounds = TextService:GetTextBoundsAsync(GetTextBoundsParams)
+
+ --> Update cursor size and position
+ Cursor.Size = UDim2.fromOffset(2, TextSize)
+ Cursor.Position = UDim2.fromOffset(TextBounds.X - 1, TextHeight * (#Slices - 1))
+
+ --> Perform auto completion pass
+ --[[DoCompletionPass()
+
+ --> Update completion position
+ local AbsoluteSize = EditorInterface.AbsoluteSize
+ local AbsolutePosition = TextCursor.AbsolutePosition
+
+ Completion.Position = UDim2.fromOffset(
+ math.clamp(AbsolutePosition.X, 0, (AbsoluteSize.X - Completion.Size.X.Offset) - 10),
+ AbsolutePosition.Y + TextSize
+ )]]
+end
+
+local function OnSelectionChanged()
+ --> Clear previous selection
+ ClearChildrenWhichAre(Selection, "Frame")
+
+ --> Nothing is selected
+ local CursorPosition = Input.CursorPosition
+ local SelectionStart = Input.SelectionStart
+ if CursorPosition == -1 or SelectionStart == -1 then
+ return
+ end
+
+ local Start = math.min(CursorPosition, SelectionStart)
+ local Finish = math.max(CursorPosition, SelectionStart)
+
+ local Text = string.sub(Input.ContentText, 1, Finish - 1)
+ local Slices = string.split(Text, "\n")
+
+ local Offset = 0
+ for Index, Slice in Slices do
+ local First = Offset
+ Offset += (#Slice + 1)
+
+ local Line = SelectionTemplate:Clone()
+ local Fill = Line.Fill
+
+ if Offset < Start then
+ Fill.BackgroundTransparency = 1
+ Line.Parent = Selection
+ continue
+ end
+
+ --> Calculate selection bounds
+ local Substring = Slice
+ if First < Start then
+ Substring = string.sub(Slice, (Start - First), #Slice)
+ elseif Index == #Slices then
+ Substring = string.sub(Slice, 1, math.max(1, Finish - First))
+ end
+
+ --> Empty lines should appear as one space thick selections
+ if Substring == "" then
+ Substring = " "
+ end
+
+ local SelectionBoundsParams = Instance.new("GetTextBoundsParams")
+ SelectionBoundsParams.Text = Substring
+ SelectionBoundsParams.Size = TextSize
+ SelectionBoundsParams.Font = Input.FontFace
+
+ local SelectionBounds = TextService:GetTextBoundsAsync(SelectionBoundsParams)
+ Fill.Size = UDim2.new(0, SelectionBounds.X, 1, 0)
+
+ --> Calculate selection offset
+ if Start > First then
+ local Prefix = string.sub(Slice, 1, (Start - First) - 1)
+ if Prefix ~= "" then
+ local OffsetBoundsParams = Instance.new("GetTextBoundsParams")
+ OffsetBoundsParams.Text = Prefix
+ OffsetBoundsParams.Size = TextSize
+ OffsetBoundsParams.Font = Input.FontFace
+
+ local OffsetBounds = TextService:GetTextBoundsAsync(OffsetBoundsParams)
+ Fill.Position = UDim2.new(0, OffsetBounds.X, 0, 0)
+ end
+ end
+
+ Line.Parent = Selection
+ end
+end
+
+local function OnSourceChanged()
+ --> Pressing enter inserts a carriage return (ROBLOX please fix)
+ local Text = Input.Text
+ local Gain = math.sign(#Text - #PreviousText)
+
+ local NoReturnCarriage = string.gsub(Text, "\r", "")
+ if NoReturnCarriage ~= Text then
+ Input.Text = NoReturnCarriage
+ return
+ end
+
+ PreviousText = Text
+
+ --> Update line counter
+ local SourceLines = #string.split(Text, "\n")
+ if SourceLines ~= Lines:Get() then
+ Lines:Set(SourceLines)
+ end
+
+ --> Run styling hooks
+ local FinalText, FinalCursor = Text, Input.CursorPosition
+ for Index, Hook in StylingHooks do
+ FinalText, FinalCursor = Hook.OnSourceChanged(FinalText, FinalCursor, Gain)
+ end
+
+ --> If a styling hook altered the final text then don't do a lexer pass as OnSourcChanged will be called again
+ if FinalText ~= Text then
+ Input.CursorPosition = -1
+ Input.Text = FinalText
+ Input.CursorPosition = FinalCursor
+
+ return
+ end
+
+ --> Perform lexer pass
+ DoLexerPass()
+end
+
+---- Public Functions ----
+
+function Editor.GetSource(): string
+ return Input.Text
+end
+
+function Editor.SetSource(Source: string)
+ Scroll:Set(0)
+ Input.Text = Source
+end
+
+---- Initialization ----
+
+function Editor.Initialize()
+ --> Remove templates
+ Selection.Line:Destroy()
+ LinesContainer.Line:Destroy()
+ CompletionContainer.Option:Destroy()
+
+ --> Sort auto complete symbols
+ for _, Symbols in SYMBOLS do
+ table.sort(Symbols, function(a, b)
+ return #a < #b
+ end)
+ end
+
+ --> Connections
+ Error.OnEmit = OnErrorEmitted
+
+ Lines:OnChange(OnLinesChanged)
+ Scroll:OnChange(OnScrollChanged)
+
+ Input:GetPropertyChangedSignal("Text"):Connect(OnSourceChanged)
+ Input:GetPropertyChangedSignal("SelectionStart"):Connect(OnSelectionChanged)
+ Input:GetPropertyChangedSignal("CursorPosition"):Connect(OnSelectionChanged)
+ Input:GetPropertyChangedSignal("CursorPosition"):Connect(OnCursorPositionChanged)
+ Input.InputChanged:Connect(function(InputObject)
+ if InputObject.UserInputType == Enum.UserInputType.MouseWheel then
+ ScrollTowards(-InputObject.Position.Z)
+ end
+ end)
+
+ RunService.PreRender:Connect(OnPreRender)
+end
+
+---- Connections ----
+
+return Editor
\ No newline at end of file
diff --git a/plugin/src/Error.rbxmx b/plugin/src/Error.rbxmx
new file mode 100644
index 0000000..5617198
--- /dev/null
+++ b/plugin/src/Error.rbxmx
@@ -0,0 +1,188 @@
+
+ true
+ null
+ nil
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.156862751
+ 0.156862751
+ 0.156862751
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Error
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ false
+ 3
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxasset://fonts/families/Ubuntu.json
+ 400
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ Text
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ true
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+ Label
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 16
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 0
+ true
+ 1
+
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+ -1
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin/src/Table.luau b/plugin/src/Table.luau
new file mode 100644
index 0000000..7b56e1b
--- /dev/null
+++ b/plugin/src/Table.luau
@@ -0,0 +1,38 @@
+local Table = {}
+
+function Table.MergeArrays(a: {any}, b: {any}): {any}
+ local Array = table.create(#a + #b)
+
+ for _, Element in a do
+ table.insert(Array, Element)
+ end
+
+ for _, Element in b do
+ table.insert(Array, Element)
+ end
+
+ return Array
+end
+
+function Table.MergeDictionaries(a: {[any]: any}, b: {[any]: any}): {[any]: any}
+ local Dictionary = table.clone(a)
+ for Key, Value in b do
+ if Dictionary[Key] then
+ warn(`Key "{Key}" already exists in the first dictionary.`)
+ continue
+ end
+
+ Dictionary[Key] = Value
+ end
+ return Dictionary
+end
+
+function Table.GetDictionaryKeys(Dictionary: {[any]: any}): {any}
+ local Keys = {}
+ for Key in Dictionary do
+ table.insert(Keys, Key)
+ end
+ return Keys
+end
+
+return Table
\ No newline at end of file
diff --git a/plugin/src/Widget.rbxmx b/plugin/src/Widget.rbxmx
new file mode 100644
index 0000000..92df568
--- /dev/null
+++ b/plugin/src/Widget.rbxmx
@@ -0,0 +1,4013 @@
+
+ true
+ null
+ nil
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.156862751
+ 0.156862751
+ 0.156862751
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ true
+ false
+ false
+ true
+ 0
+ Widget
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.176470593
+ 0.176470593
+ 0.176470593
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Side
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ -208
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 250
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 2
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ rbxassetid://5051528605
+
+ 0
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0.699999988
+ true
+ 0
+ Shadow
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 1
+ 0
+ 0
+ 0
+
+ 0
+ [null]
+ 0
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 8
+ 1
+ 0
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ true
+ false
+ false
+
+ 1
+ 1
+ 1
+
+ 1
+ true
+ 0
+ Content
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ Padding
+
+
0
+ 10
+
+
+ 0
+ 10
+
+
+ 0
+ 10
+
+
+ 0
+ 10
+
+ -1
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Top
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Title
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 22
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Search
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 200
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 0
+
+ 0
+
+ 0.0980392173
+ 0.0980392173
+ 0.0980392173
+
+ false
+ true
+ 0
+ UIStroke
+ -1
+
+ 1
+ 0
+
+
+ -
+
+ true
+
+ 0.5
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ true
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 500
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ false
+ Input
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0.43921569
+ 0.43921569
+ 0.43921569
+
+ Search
+
+ 0.5
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+ true
+
+ 0.899999976
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+
+
+ 1
+ 1
+ 1
+
+ 0
+ true
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 1
+ true
+ 1
+
+
+
+
+ -
+
+
+ 0
+ false
+ 1
+ 0
+ 0
+ 0
+ UIListLayout
+
+
0
+ 10
+
+ 2
+ -1
+
+ 1
+ 1
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ true
+ false
+ false
+ true
+ 0
+ Files
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ 1
+ 0
+ 0
+ 0
+ UIListLayout
+
+
0
+ 10
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ File
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 24
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ Title
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+ Example File
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 16
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 1
+ true
+ 1
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Buttons
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ 0
+ 2
+ 0
+ 0
+ UIListLayout
+
+
0
+ 5
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.117647059
+ 0.117647059
+ 0.117647059
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://16483084978
+
+ 1
+ 1
+ 1
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0
+ true
+ 0
+ false
+ Delete
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ [null]
+ 0
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+ 0
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 1
+ 0
+
+ 0
+ false
+ 0
+ UIAspectRatioConstraint
+ -1
+
+
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.117647059
+ 0.117647059
+ 0.117647059
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://16483089645
+
+ 1
+ 1
+ 1
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0
+ true
+ 0
+ false
+ Edit
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ [null]
+ 0
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+ 0
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 1
+ 0
+
+ 0
+ false
+ 0
+ UIAspectRatioConstraint
+ -1
+
+
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.117647059
+ 0.117647059
+ 0.117647059
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://16494932101
+
+ 1
+ 1
+ 1
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0
+ true
+ 0
+ false
+ Generate
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ [null]
+ 0
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+ 0
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 1
+ 0
+
+ 0
+ false
+ 0
+ UIAspectRatioConstraint
+ -1
+
+
+
+
+
+
+
+
+ -
+
+ false
+
+ 0
+ 1
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Bottom
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 1
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 22
+
+ 0
+ -1
+ 0
+
+ true
+ 2
+
+ -
+
+
+ 0
+ false
+ 1
+ 0
+ 0
+ 0
+ UIListLayout
+
+
0
+ 10
+
+ 2
+ -1
+
+ 2
+ 0
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 2
+ Buttons
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ 0
+ 1
+ 1
+ 0
+ UIListLayout
+
+
0
+ 10
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.117647059
+ 0.117647059
+ 0.117647059
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 1
+ 1
+
+
+ -1
+ false
+ Save
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ Save
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 2
+ 1
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.117647059
+ 0.117647059
+ 0.117647059
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ false
+ Cancel
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ Cancel
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 2
+ 1
+ false
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Generate
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 72
+
+ 0
+ -1
+ 0
+
+ false
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 0
+
+ 0
+
+ 0.0980392173
+ 0.0980392173
+ 0.0980392173
+
+ false
+ true
+ 0
+ UIStroke
+ -1
+
+ 1
+ 0
+
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+ -1
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 1
+ 1
+
+
+ -1
+ Hint
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ true
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 30
+
+ 0
+ -1
+
+ Selected
+ReplicatedStroage.bla.bla.bla]]>
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 1
+ true
+ 2
+ 1
+ true
+ 1
+
+
+ -
+
+
+ 0
+ false
+ 1
+ 0
+ 0
+ 0
+ UIListLayout
+
+
0
+ 2
+
+ 2
+ -1
+
+ 0
+ 3
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 1
+ Buttons
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0.899999976
+ 0
+ 0
+ 18
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.196078435
+ 0.196078435
+ 0.196078435
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 1
+ 1
+
+
+ -1
+ false
+ Cancel
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0.899999976
+ 0
+ 0
+ 18
+
+ 0
+ -1
+ 0
+
+ Cancel
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 2
+ 1
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ true
+ 0
+
+ 0.196078435
+ 0.196078435
+ 0.196078435
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 1
+ 1
+
+
+ -1
+ false
+ Generate
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0.899999976
+ 0
+ 0
+ 18
+
+ 0
+ -1
+ 0
+
+ Generate
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0.5
+ 0
+ false
+ 2
+ 1
+ true
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+
+ -
+
+
+ 0
+ false
+ 0
+ 1
+ 1
+ 0
+ UIListLayout
+
+
0
+ 10
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 1
+ Save
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 22
+
+ 0
+ -1
+ 0
+
+ false
+ 1
+
+ -
+
+
+ 0
+
+
0
+ 6
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ 0
+
+ 0
+
+ 0.0980392173
+ 0.0980392173
+ 0.0980392173
+
+ false
+ true
+ 0
+ UIStroke
+ -1
+
+ 1
+ 0
+
+
+ -
+
+ true
+
+ 0.5
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ true
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 500
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ false
+ Input
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0.43921569
+ 0.43921569
+ 0.43921569
+
+ Name
+
+ 0.5
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ true
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+ true
+
+ 0.899999976
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+
+
+ 1
+ 1
+ 1
+
+ 0
+ true
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 1
+ true
+ 1
+
+
+
+
+
+ -
+
+ true
+
+ 1
+ 0
+
+
+ true
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://16482966093
+
+ 1
+ 1
+ 1
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0
+ true
+ 0
+ false
+ Expand
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 1
+ -10
+ 0
+ 10
+
+
+ 0
+ [null]
+ 0
+ 0
+ true
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 22
+ 1
+ 0
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+ 0
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+ -
+
+ 1
+ 0
+
+ 0
+ false
+ 0
+ UIAspectRatioConstraint
+ -1
+
+
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Editor
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 42
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ -42
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Lines
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 42
+ 100
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 8
+
+
+ 0
+ 4
+
+
+ 0
+ 4
+
+
+ 0
+ 8
+
+ -1
+
+
+
+ -
+
+
+ 0
+ false
+ 1
+ 1
+ 0
+ 0
+ UIListLayout
+
+
0
+ 3
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxasset://fonts/families/GothamSSm.json
+ 400
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ Line
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 16
+
+ 0
+ -1
+
+ 000
+
+ 0.470588267
+ 0.470588267
+ 0.470588267
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 1
+ 1
+ true
+ 1
+
+
+
+ -
+
+
+ 0
+ false
+ 0
+ 1
+ 1
+ 0
+ UIListLayout
+
+
0
+ 0
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Text
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 20
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 100
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+
+ 0
+ 8
+
+ -1
+
+
+
+ -
+
+ true
+
+ 0
+ 0
+
+
+ true
+ 2
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 0
+ 1.20000005
+
+
+ -1
+ true
+ Input
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0.699999988
+ 0.699999988
+ 0.699999988
+
+
+
+ 0
+ 0
+ 0
+ 0
+
+ true
+ [null]
+ 0
+ true
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+ true
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+
+
+ 1
+ 1
+ 1
+
+ 0
+ true
+ false
+ 16
+
+ 0
+ 0
+ 0
+
+ 1
+ 1
+ 0
+ false
+ 0
+ 0
+ true
+ 2
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Cursor
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 1
+ 0
+ 14
+
+ 0
+ -1
+ 0
+
+ false
+ 4
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 400
+
+
+ true
+ 0
+ 1.20000005
+
+
+ -1
+ Display
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ true
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+
+
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 16
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 0
+ true
+ 0
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Selection
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ -1
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+
+ 0
+ false
+ 1
+ 1
+ 0
+ 0
+ UIListLayout
+
+
0
+ 0
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.200000018
+ 0.411764741
+ 0.627451003
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Line
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 19
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.200000018
+ 0.411764741
+ 0.627451003
+
+ 0.600000024
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Fill
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+
+
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0
+ 0
+ 0
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Overlay
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 1
+ 0
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 2
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Completion
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0.252777785
+ 0
+ 0.0250000004
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 200
+ 0
+ 0
+
+ 0
+ -1
+ 0
+
+ false
+ 5
+
+ -
+
+ 0
+
+ 0
+
+ 0.470588267
+ 0.470588267
+ 0.470588267
+
+ false
+ true
+ 0
+ UIStroke
+ -1
+
+ 1
+ 0
+
+
+ -
+
+
+ 0
+
+
0
+ 2
+
+ false
+ UICorner
+ -1
+
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 0.137254909
+ 0.137254909
+ 0.137254909
+
+ 0
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ true
+ 0
+ Option
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 1
+ 0
+ 0
+ 16
+
+ 0
+ -1
+ 0
+
+ true
+ 1
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+ rbxassetid://16506695241
+
+ 1
+ 1
+ 1
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+ 0
+ true
+ 0
+ Icon
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 0
+ 0
+ 0
+
+ 1
+ [null]
+ 0
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 16
+ 0
+ 16
+
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 1
+ -1
+
+
+ 1
+ 0
+ 1
+ 0
+
+ true
+ 1
+
+
+ -
+
+ false
+
+ 0
+ 0
+
+
+ true
+ 0
+
+ 1
+ 1
+ 1
+
+ 1
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ false
+ false
+ false
+
+ rbxassetid://12187374954
+ 500
+
+
+ true
+ 0
+ 1
+
+
+ -1
+ Text
+
[null]
+ [null]
+ [null]
+ [null]
+
+ 0
+ 20
+ 0
+ 0
+
+ false
+ [null]
+ 0
+ false
+ 0
+ 0
+ 0
+ 0
+ false
+ [null]
+ 0
+
+ 0
+ 200
+ 1
+ 0
+
+ 0
+ -1
+
+ struct
+
+ 1
+ 1
+ 1
+
+ 0
+ false
+ 14
+
+ 0
+ 0
+ 0
+
+ 1
+ 0
+ 0
+ false
+ 0
+ 1
+ true
+ 1
+
+
+ -
+
+
+ 0
+ false
+ 0
+ 1
+ 1
+ 0
+ UIListLayout
+
+
0
+ 4
+
+ 2
+ -1
+
+ 1
+ 0
+ false
+
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 0
+
+
+ 0
+ 5
+
+
+ 0
+ 5
+
+
+ 0
+ 0
+
+ -1
+
+
+
+ -
+
+
+ 0
+
+
0
+ 4
+
+ false
+ UICorner
+ -1
+
+
+
+
+ -
+
+
+ 0
+ false
+ UIPadding
+
+
0
+ 2
+
+
+ 0
+ 0
+
+
+ 0
+ 0
+
+
+ 0
+ 2
+
+ -1
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin/src/init.luau b/plugin/src/init.server.luau
similarity index 51%
rename from plugin/src/init.luau
rename to plugin/src/init.server.luau
index 7038c28..e56c39a 100644
--- a/plugin/src/init.luau
+++ b/plugin/src/init.server.luau
@@ -8,6 +8,7 @@
local ServerStorage = game:GetService("ServerStorage")
local UserInputService = game:GetService("UserInputService")
+local ContextActionService = game:GetService("ContextActionService")
local TweenService = game:GetService("TweenService")
local TextService = game:GetService("TextService")
local RunService = game:GetService("RunService")
@@ -16,41 +17,25 @@ local Selection = game:GetService("Selection")
---- Imports ----
local State = require("./State")
-local Lexer = require("../../src/Lexer")
+local Editor = require("./Editor")
+
local Parser = require("../../src/Parser")
local Generator = require("../../src/Generator")
---- Settings ----
-type A = {
- Identifier: number,
-}
-
-local EDITOR_COLORS = {
- Text = Color3.fromHex("#FFFFFF"),
- Keyword = Color3.fromHex("#6796E6"),
- Primitive = Color3.fromHex("#4EC9B0"),
- Identifier = Color3.fromHex("#9CDCFE"),
-
- Class = Color3.fromHex("#B5CEA8"),
- Number = Color3.fromHex("#B5CEA8"),
- String = Color3.fromHex("#ADF195"),
- Bracket = Color3.fromHex("#FFFFFF"),
- Boolean = Color3.fromHex("#B5CEA8"),
-
- Error = Color3.fromHex("#FF5050"),
-}
-
local FILES_FOLDER = "BLINK_CONFIGURATION_FILES"
local TEMPLATE_FILE = {
Name = "Template",
- Source = [[type Example = u8
-event MyEvent = {
- From = Server,
- Type = Reliable,
- Call = SingleSync,
- Data = Example
-}]]
+ Source = table.concat({
+ "type Example = u8",
+ "event MyEvent = {",
+ "\tFrom = Server,",
+ "\tType = Reliable,",
+ "\tCall = SingleSync,",
+ "\tData = Example",
+ "}"
+ }, "\n")
}
local ERROR_WIDGET = DockWidgetPluginGuiInfo.new(
@@ -77,24 +62,6 @@ local SAVE_COLOR = Color3.fromRGB(0, 100, 0)
local BUTTON_COLOR = Color3.fromRGB(30, 30, 30)
local EXPAND_TWEEN = TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
-local SCROLL_LINES = 2
-local CURSOR_BLINK_RATE = 0.5
-
-local FIELDS_TYPES = {
- struct = true,
- event = true,
- ["function"] = true,
-}
-
-local BRACKETS_TYPES = {
- OpenBrackets = true,
- CloseBrackets = true,
- OpenCurlyBrackets = true,
- CloseCurlyBrackets = true,
- OpenSquareBrackets = true,
- CloseSquareBrackets = true
-}
-
---- Constants ----
local Toolbar = plugin:CreateToolbar("Blink Suite")
@@ -102,12 +69,10 @@ local EditorButton = Toolbar:CreateButton("Editor", "Opens the configuration edi
EditorButton.ClickableWhenViewportHidden = true
local Template = script.Widget
-local LineTemplate: TextLabel = Template.Editor.Lines.Line:Clone()
local FileTemplate = Template.Side.Content.Top.Files.File:Clone()
--> Remove templates
--Template.Editor.Text.Line:Destroy()
-Template.Editor.Lines.Line:Destroy()
Template.Side.Content.Top.Files.File:Destroy()
local ErrorWidget = plugin:CreateDockWidgetPluginGui("Generation Error", ERROR_WIDGET)
@@ -125,42 +90,34 @@ EditorWidget.Name = "Blink Editor"
EditorWidget.Title = "Configuration Editor"
EditorWidget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
-local EditorInterface = Template:Clone()
+local EditorInterface = script.Widget
EditorInterface.Size = UDim2.fromScale(1, 1)
EditorInterface.Parent = EditorWidget
local Side = EditorInterface.Side
-local Editor = EditorInterface.Editor
-local Overlay: Frame = EditorInterface.Overlay
+local Overlay = EditorInterface.Overlay
local Content = Side.Content
-local Expand: ImageButton = Side.Expand
+local Expand = Side.Expand
local Top = Content.Top
local Bottom = Content.Bottom
-local Files: Frame = Top.Files
-local Search: TextBox = Top.Title.Search.Input
+local Files = Top.Files
+local Search = Top.Title.Search.Input
-local Prompt: Frame = Bottom.Save
+local Prompt = Bottom.Save
local Buttons = Bottom.Buttons
-local GeneratePrompt: Frame = Bottom.Generate
-local GenerateButtons: Frame = GeneratePrompt:FindFirstChild("Buttons") :: Frame
-local Hint: TextLabel = GeneratePrompt:FindFirstChild("Hint") :: TextLabel
-local Generate: TextButton = GenerateButtons:FindFirstChild("Generate") :: TextButton
-local CancelGenerate: TextButton = GenerateButtons:FindFirstChild("Cancel") :: TextButton
-
-local Input: TextBox = Prompt:FindFirstChild("Input") :: TextBox
-local Save: TextButton = Buttons.Save
-local Cancel: TextButton = Buttons.Cancel
+local GeneratePrompt = Bottom.Generate
+local GenerateButtons = GeneratePrompt.Buttons
+local Hint = GeneratePrompt.Hint
+local Generate = GenerateButtons.Generate
+local CancelGenerate = GenerateButtons.Cancel
-local TextInput: TextBox = Editor.Text.Input
-local TextCursor: Frame = Editor.Text.Cursor
-local TextDisplay: TextLabel = Editor.Text.Display
-local NumberLines: Frame = Editor.Lines
-
-local TextLineHeight = (TextInput.TextSize + 3)
+local Input = Prompt.Input
+local Save = Buttons.Save
+local Cancel = Buttons.Cancel
--> Tweens
local Tweens = {
@@ -179,23 +136,17 @@ local Tweens = {
}
}
-local SourceLexer = Lexer.new("Highlighting")
-
---- Variables ----
local Saving = false
-local Scroll = State.new(0)
+
local Expanded = State.new(false)
local Selected: State.Class = State.new(nil :: any)
local Generating: State.Class = State.new(nil :: any)
local WasOpened = false
-
-local Lines = 0
local Editing: string?;
-local CursorTimer = 0
-
---- Private Functions ----
--> Interface utility functions
@@ -212,12 +163,11 @@ local function PlayTweens(Tweens: {[string]: Tween})
end
end
-local function CreateScript(Name: string, Source: string, RunContent: Enum.RunContext, Parent: Instance)
- local Script = Instance.new("Script")
- Script.Name = Name
- Script.Source = Source
- Script.RunContext = RunContent
- Script.Parent = Parent
+local function CreateModuleScript(Name: string, Source: string, Parent: Instance)
+ local ModuleScript = Instance.new("ModuleScript")
+ ModuleScript.Name = Name
+ ModuleScript.Source = Source
+ ModuleScript.Parent = Parent
end
local function RequestScriptPermissions(): boolean
@@ -268,10 +218,11 @@ local function GenerateFile(File: StringValue, Directory: Instance)
return
end
- local ServerOutput = Generator("Server", AbstractSyntaxTree, UserOptions)
- local ClientOutput = Generator("Client", AbstractSyntaxTree, UserOptions)
+ local ServerSource = Generator.Generate("Server", AbstractSyntaxTree, UserOptions)
+ local ClientSource = Generator.Generate("Client", AbstractSyntaxTree, UserOptions)
+ local TypesSource = Generator.GenerateTypeDefinitions("Server", AbstractSyntaxTree, UserOptions)
- local BlinkFolder: Folder = Directory:FindFirstChild("Folder") :: Folder
+ local BlinkFolder: Folder = Directory:FindFirstChild("Blink") :: Folder
if not BlinkFolder then
local Folder = Instance.new("Folder")
Folder.Name = "Blink"
@@ -279,16 +230,19 @@ local function GenerateFile(File: StringValue, Directory: Instance)
BlinkFolder = Folder
end
+ --> Clear previous files
BlinkFolder:ClearAllChildren()
- CreateScript("Server", ServerOutput, Enum.RunContext.Server, BlinkFolder)
- CreateScript("Client", ClientOutput, Enum.RunContext.Client, BlinkFolder)
+
+ --> Generate output files
+ CreateModuleScript("Types", TypesSource, BlinkFolder)
+ CreateModuleScript("Server", ServerSource, BlinkFolder)
+ CreateModuleScript("Client", ClientSource, BlinkFolder)
end
local function LoadFile(File: StringValue)
- Scroll:Set(0)
Expanded:Set(false)
Editing = File.Name
- TextInput.Text = File.Value
+ Editor.SetSource(File.Value)
end
local function LoadFiles()
@@ -354,111 +308,8 @@ local function CreateTemplateFile()
LoadFile(GetSaveFolder():FindFirstChild(TEMPLATE_FILE.Name) :: StringValue)
end
---> Editor utility functions
-local function WrapColor(Text: string, Color: Color3): string
- return `{Text}`
-end
-
-local function GetEditorSource(): string
- return TextInput.ContentText
-end
-
-local function DoLexerPass()
- local Source = GetEditorSource()
- local Display = ""
-
- --> Initiialize lexer
- SourceLexer:Initialize(Source)
-
- --> Highlight state
- local Keyword = "none"
- local IsField = false
- --local IsAssigning = false
-
- while true do
- local Success, Error, Token = pcall(function()
- return nil, SourceLexer:GetNextToken()
- end)
-
- if not Success then
- warn(`Lexer error: {Error}`)
- break
- end
-
- if not Token then
- break
- end
-
- local Type: Lexer.Types = Token.Type
- local Value = Token.Value
-
- if Type == "Keyword" then
- Keyword = Value
- Display ..= WrapColor(Value, EDITOR_COLORS.Keyword)
- elseif Type == "Primitive" then
- Display ..= WrapColor(Value, EDITOR_COLORS.Primitive)
- elseif Type == "Identifier" then
- Display ..= WrapColor(Value, IsField and EDITOR_COLORS.Text or EDITOR_COLORS.Identifier)
- elseif Type == "Array" or Type == "Range" then
- --> Exact size array/range
- local Single = string.match(Value, "%[(%d+)%]") or string.match(Value, "%((%d+)%)")
- if Single then
- Display ..= string.gsub(Value, Single, WrapColor(Single, EDITOR_COLORS.Number))
- continue
- end
-
- --> Variable size array/range
- local Lower = string.match(Value, "%[(%d+)") or string.match(Value, "%((%d+)")
- local Upper = string.match(Value, "(%d+)%]") or string.match(Value, "(%d+)%)")
- if Lower and Upper then
- Display ..= `{string.sub(Value, 1, 1)}{WrapColor(Lower, EDITOR_COLORS.Number)}..{WrapColor(Upper, EDITOR_COLORS.Number)}{string.sub(Value, #Value, #Value)}`
- continue
- end
-
- Display ..= Value
- elseif BRACKETS_TYPES[Type] then
- if Type == "CloseCurlyBrackets" then
- IsField = false
- end
-
- Display ..= WrapColor(Value, EDITOR_COLORS.Bracket)
- elseif Type == "Class" then
- Display ..= `({WrapColor(string.sub(Value, 2, #Value - 1), EDITOR_COLORS.Class)})`
- elseif Type == "String" then
- Display ..= WrapColor(Value, EDITOR_COLORS.String)
- elseif Type == "Boolean" then
- Display ..= WrapColor(Value, EDITOR_COLORS.Boolean)
- elseif Type == "Unknown" then
- --> Reset all state
- IsField = false
- Display ..= WrapColor(Value, EDITOR_COLORS.Error)
- else
- Display ..= Value
- end
-
- if Type == "Whitespace" then
- continue
- end
-
- IsField = (Type == "Comma" or Type == "OpenCurlyBrackets") and FIELDS_TYPES[Keyword]
- --IsAssigning = (Type == "Assign")
- end
-
- TextDisplay.Text = Display
-end
-
-local function ScrollEditor(Direction: number)
- local Value = Scroll:Get()
- local Maximum = math.max(1, (TextInput.TextBounds.Y - Editor.AbsoluteSize.Y) // TextLineHeight + SCROLL_LINES)
- Scroll:Set(math.clamp(Value + (Direction * SCROLL_LINES), 0, Maximum))
-end
-
---- Public Functions ----
-local function OnScroll(Value: number)
- Editor.Position = UDim2.fromOffset(Editor.Position.X.Offset, TextLineHeight * -Value)
-end
-
local function OnSearch(PressedEnter: boolean)
if not PressedEnter then
return
@@ -479,55 +330,6 @@ local function OnSearch(PressedEnter: boolean)
end
end
-local function OnCursorMoved()
- local CursorPosition = TextInput.CursorPosition
- if CursorPosition == -1 then
- TextCursor.Visible = false
- return
- end
-
- local Size = TextInput.TextSize
- local Text = string.sub(TextInput.ContentText, 1, CursorPosition - 1)
- local Slices = string.split(Text, "\n")
- local NewLines = math.max(#Slices, 1)
-
- --> Calculate current end line position
- local Line = Slices[#Slices] or ""
- local GetTextBoundsParams = Instance.new("GetTextBoundsParams")
- GetTextBoundsParams.Text = Line
- GetTextBoundsParams.Size = Size
- GetTextBoundsParams.Font = TextInput.FontFace
- local TextBounds = TextService:GetTextBoundsAsync(GetTextBoundsParams)
-
- --> Update cursor size and position
- --TextCursor.Visible = true
- TextCursor.Size = UDim2.fromOffset(2, Size)
- TextCursor.Position = UDim2.fromOffset(TextBounds.X - 1, TextLineHeight * (NewLines - 1))
-end
-
-local function OnSourceChanged()
- --> Pressing enter inserts a carriage return (ROBLOX please fix)
- local ContentText = TextInput.ContentText
- ContentText = string.gsub(ContentText, "\r", "")
- TextInput.Text = ContentText
-
- --> Update line counter
- local ContentLines = #string.split(ContentText, "\n")
- if ContentLines ~= Lines then
- Lines = ContentLines
- ClearChildrenWhichAre(NumberLines, "TextLabel")
-
- for Index = 1, Lines do
- local Line = LineTemplate:Clone()
- Line.Text = tostring(Index)
- Line.Parent = NumberLines
- end
- end
-
- --> Perform lexer pass
- DoLexerPass()
-end
-
local function OnSaveCompleted()
Saving = false
Prompt.Visible = false
@@ -542,7 +344,7 @@ local function OnSaveActivated()
return
end
- SaveFile(Name, GetEditorSource())
+ SaveFile(Name, Editor.GetSource())
LoadFiles()
OnSaveCompleted()
@@ -570,25 +372,11 @@ local function OnEditorButtonClicked()
EditorWidget.Enabled = not EditorWidget.Enabled
end
-local function OnPreRender(DeltaTime: number)
- if TextInput.CursorPosition == -1 then
- return
- end
-
- CursorTimer += DeltaTime
- if CursorTimer < CURSOR_BLINK_RATE then
- return
- end
-
- CursorTimer -= CURSOR_BLINK_RATE
- TextCursor.Visible = not TextCursor.Visible
-end
-
---- Initialization ----
----- Connections ----
+Editor.Initialize()
-Scroll:OnChange(OnScroll)
+---- Connections ----
Expanded:OnChange(function(Value)
PlayTweens(Value and Tweens.Editor.Expand or Tweens.Editor.Retract)
@@ -633,18 +421,4 @@ Search.FocusLost:Connect(OnSearch)
Save.Activated:Connect(OnSaveActivated)
Cancel.Activated:Connect(OnSaveCompleted)
Expand.Activated:Connect(OnExpandActivated)
-EditorButton.Click:Connect(OnEditorButtonClicked)
-
-TextInput:GetPropertyChangedSignal("Text"):Connect(OnSourceChanged)
-TextInput:GetPropertyChangedSignal("CursorPosition"):Connect(OnCursorMoved)
-TextInput.InputChanged:Connect(function(InputObject)
- if not Editing then
- return
- end
-
- if InputObject.UserInputType == Enum.UserInputType.MouseWheel then
- ScrollEditor(-InputObject.Position.Z)
- end
-end)
-
-RunService.PreRender:Connect(OnPreRender)
+EditorButton.Click:Connect(OnEditorButtonClicked)
\ No newline at end of file
diff --git a/src/Generator/Prefabs.luau b/src/Generator/Prefabs.luau
index 1e3fd6b..c276350 100644
--- a/src/Generator/Prefabs.luau
+++ b/src/Generator/Prefabs.luau
@@ -74,7 +74,7 @@ local Asserts = {
Upper = `error("Something has gone wrong")`,
Exact = `if (not {Variable}) or typeof({Variable}) ~= "Instance" then error(\`Expected an Instance, got \{typeof({Variable})} instead.\`) end\nif not {Variable}:IsA("{Class}") then error(\`Expected an Instance of type "{Class}", got "\{{Variable}.ClassName}" instead.\`) end`,
}
- end
+ end,
}
local Structures = {
Array = nil :: any
@@ -99,7 +99,11 @@ local function GeneratePrimitivePrefab(Prefab: TypePrefab, AssertGenerator: Asse
local IsVariableSize = (Range and Range.Max ~= Range.Min)
local function GenerateValidation(Block: Block, Variable: string)
- if Range and AssertGenerator then
+ if not AssertGenerator then
+ return
+ end
+
+ if Range then
local Assert = AssertGenerator(Variable, Range.Min, Range.Max)
if not IsVariableSize and Assert.Exact then
Block:Line(Assert.Exact)
@@ -107,7 +111,7 @@ local function GeneratePrimitivePrefab(Prefab: TypePrefab, AssertGenerator: Asse
Block:Line(Assert.Lower)
Block:Line(Assert.Upper)
end
- elseif Primitive == "Instance" and AssertGenerator then
+ elseif Primitive == "Instance" then
local Assert = AssertGenerator(Variable, Class)
if Value.Optional and Block == Write then
Block:Compare(Variable, "nil", "Not")
@@ -432,6 +436,24 @@ do
}
end
+--> unknown
+do
+ Types.unknown = {
+ Read = function(Variable: string, Block: Block)
+ Block:Line("RecieveInstanceCursor += 1")
+ Block:Line(`{Variable} = RecieveInstances[RecieveInstanceCursor]`)
+ end,
+ Write = function(Value: string, Block: Block)
+ Block:Line(`table.insert(SendInstances, {Value})`)
+ end,
+ }
+
+ Primitives.unknown = {
+ Type = "any",
+ Generate = GeneratePrimitivePrefab(Types.unknown)
+ }
+end
+
return {
Types = Types,
Asserts = Asserts,
diff --git a/src/Generator/init.luau b/src/Generator/init.luau
index e1b4183..42f3acb 100644
--- a/src/Generator/init.luau
+++ b/src/Generator/init.luau
@@ -573,7 +573,14 @@ function Generators.UserType(Declaration: Type, Read: Blocks.Block, Write: Block
local Value = Declaration.Value
local Optional = Value.Optional
local Variable = Variable or "Value"
- local IsInstance = (Declaration.Type == "TypeDeclaration" and ((Value :: any).Primitive :: string) == "Instance")
+ local IsInstance, IsUnknown;
+
+ if Declaration.Type == "TypeDeclaration" then
+ local Primitive: string = (Value :: any).Primitive
+ IsUnknown = (Primitive == "unknown")
+ IsInstance = (Primitive == "Instance")
+ Optional = if IsUnknown then true else Optional
+ end
if not _G.BUNDLED then
Read:Comment(`{Variable}: {Value.Identifier}`)
diff --git a/src/Lexer.luau b/src/Lexer.luau
index 6681ff8..441a15b 100644
--- a/src/Lexer.luau
+++ b/src/Lexer.luau
@@ -2,6 +2,7 @@
--!optimize 2
local Error = require("./Modules/Error")
+local Settings = require("./Settings")
export type Types =
"Comma" | "OpenBrackets" | "CloseBrackets" | "OpenCurlyBrackets" | "CloseCurlyBrackets" | "OpenSquareBrackets" | "CloseSquareBrackets" --> Structs & enums
@@ -32,37 +33,8 @@ export type Lexer = {
export type Mode = "Parsing" | "Highlighting"
-local Primitives = {
- u8 = true,
- u16 = true,
- u32 = true,
- i8 = true,
- i16 = true,
- i32 = true,
- f16 = true,
- f32 = true,
- f64 = true,
- boolean = true,
- string = true,
- vector = true,
- buffer = true,
- Color3 = true,
- CFrame = true,
- Instance = true,
-}
-
-local Keywords = {
- map = true,
- type = true,
- enum = true,
- struct = true,
-
- event = true,
- ["function"] = true,
-
- scope = true,
- option = true,
-}
+local Primitives = Settings.Primtives
+local Keywords = Settings.Keywords
local Booleans = {
["true"] = true,
diff --git a/src/Modules/Error.luau b/src/Modules/Error.luau
index c71d4a7..85e6092 100644
--- a/src/Modules/Error.luau
+++ b/src/Modules/Error.luau
@@ -24,6 +24,12 @@ type Slice = {
Underlines: number,
}
+export type Label = {
+ Span: Span,
+ Text: string,
+ Type: "Primary" | "Secondary"
+}
+
type Color = "red" | "green" | "blue" | "black" | "white" | "cyan" | "purple" | "yellow" | "reset"
export type Span = {
@@ -43,6 +49,8 @@ local Colors = {
}
local Error = {
+ OnEmit = nil,
+
LexerUnexpectedToken = 1001,
ParserUnexpectedEndOfFile = 2001,
@@ -59,12 +67,14 @@ local Error = {
AnalyzeNestedScope = 3006,
AnalyzeUnknownReference = 3007,
AnalyzeInvalidRangeType = 3008,
- AnalyzeInvalidRange = 3009
+ AnalyzeInvalidRange = 3009,
+ AnalyzeDuplicateDeclaration = 3010
}
Error.__index = Error
export type Class = typeof(setmetatable({} :: {
+ Labels: {Label},
Source: {string},
Message: string,
}, Error))
@@ -89,6 +99,7 @@ function Error.new(Code: number, Source: string, Message: string): Class
Content ..= `\n{INDENT}┌─{SPACE}input.blink`
return setmetatable({
+ Labels = {},
Source = string.split(Source, "\n"),
Message = Content
}, Error)
@@ -125,19 +136,44 @@ end
function Error.Primary(self: Class, Span: Span, Text: string): Class
local Slice = self:Slice(Span)
+
+ --> Analyze hook
+ table.insert(self.Labels, {
+ Span = Span,
+ Text = Text,
+ Type = "Primary"
+ })
+
+ --> Construct message
self.Message ..= `\n{Color(string.format("%03i", Slice.Line), "blue")}{SPACE}│{SPACE}{Slice.Text}`
self.Message ..= `\n{INDENT}│{SPACE}{string.rep(SPACE, Slice.Spaces)}{Color(`{string.rep("^", Slice.Underlines)}{SPACE}{Fix(Text)}`, "red")}`
+
return self
end
function Error.Secondary(self: Class, Span: Span, Text: string): Class
local Slice = self:Slice(Span)
+
+ --> Analyze hook
+ table.insert(self.Labels, {
+ Span = Span,
+ Text = Text,
+ Type = "Secondary"
+ })
+
+ --> Construct message
self.Message ..= `\n{Color(string.format("%03i", Slice.Line), "blue")}{SPACE}│{SPACE}{Slice.Text}`
self.Message ..= `\n{INDENT}│{SPACE}{string.rep(SPACE, Slice.Spaces)}{Color(`{string.rep("^", Slice.Underlines)}{SPACE}{Fix(Text)}`, "blue")}`
+
return self
end
function Error.Emit(self: Class): never
+ local OnEmit: (({Label}) -> ())? = Error.OnEmit
+ if OnEmit then
+ OnEmit(self.Labels)
+ end
+
error(self.Message, 2)
end
diff --git a/src/Parser.luau b/src/Parser.luau
index 729910d..9ac2f8f 100644
--- a/src/Parser.luau
+++ b/src/Parser.luau
@@ -6,73 +6,96 @@ local Lexer = require("./Lexer")
local Error = require("./Modules/Error")
local Table = require("./Modules/Table")
+type Token = Lexer.Token
type NumberRange = {Min: number, Max: number}
type Declarations = "TypeReference" | "MapDeclaration" | "TypeDeclaration" | "EnumDeclaration" | "StructDeclaration"
| "TupleDeclaration" | "EventDeclaration" | "FunctionDeclaration" | "ScopeDeclaration"
-type Node = {
+type Node = {
Type: Type,
- Value: Value
+ Value: Value,
+ Tokens: Tokens
}
-export type Body = Node<"Body", {Declaration}>
+export type Body = Node<"Body", {Declaration}, {}>
export type Declaration = Node
export type TypeReference = Node<"TypeReference", {
Scope: string?,
Identifier: string,
+ Optional: boolean,
+
Reference: string,
-
+
Array: NumberRange?,
Range: NumberRange?,
- Optional: boolean,
+}, {
+ Identifier: Token,
}>
-export type MapDeclaration = Node<"MapDeclaration", {
+export type TypeDeclaration = Node<"TypeDeclaration", {
Scope: string?,
Identifier: string,
+ Optional: boolean,
- Key: Declaration,
- Value: Declaration,
+ Primitive: string,
+ Class: string?,
Array: NumberRange?,
- Optional: boolean,
+ Range: NumberRange?,
+}, {
+ Identifier: Token,
+ Value: Token,
}>
-export type TypeDeclaration = Node<"TypeDeclaration", {
+export type MapDeclaration = Node<"MapDeclaration", {
Scope: string?,
Identifier: string,
- Primitive: string,
+ Optional: boolean,
+
+ Key: Declaration,
+ Value: Declaration,
- Class: string?,
Array: NumberRange?,
- Range: NumberRange?,
- Optional: boolean,
+}, {
+ Identifier: Token,
}>
export type EnumDeclaration = Node<"EnumDeclaration", {
Scope: string?,
Identifier: string,
- Enums: {string},
Optional: boolean,
+
+ Enums: {string},
+}, {
+ Identifier: Token,
}>
export type StructDeclaration = Node<"StructDeclaration", {
Scope: string?,
Identifier: string,
- Fields: {Declaration},
Optional: boolean,
+
+ Fields: {Declaration},
+}, {
+ Identifier: Token,
}>
export type TupleDeclaration = Node<"TupleDeclaration", {
+ Scope: string?,
Identifier: string,
+ Optional: boolean,
Values: {Declaration},
- Optional: boolean
+}, {
+ Identifier: Token
}>
export type EventDeclaration = Node<"EventDeclaration", {
@@ -83,6 +106,14 @@ export type EventDeclaration = Node<"EventDeclaration", {
Type: "Reliable" | "Unreliable",
Call: "SingleSync" | "ManySync" | "SingleAsync" | "ManyAsync",
Data: Declaration
+}, {
+ Identifier: Token,
+ Fields: {
+ {
+ Field: Token,
+ Value: Token
+ }
+ },
}>
export type FunctionDeclaration = Node<"FunctionDeclaration", {
@@ -92,12 +123,23 @@ export type FunctionDeclaration = Node<"FunctionDeclaration", {
Yield: "Future" | "Promise" | "Coroutine",
Data: Declaration,
Return: Declaration
+}, {
+ Identifier: Token,
+ Fields: {
+ {
+ Field: Token,
+ Value: Token
+ }
+ },
}>
export type ScopeDeclaration = Node<"ScopeDeclaration", {
+ Scope: string?,
Identifier: string,
- Declarations: {Declaration},
Optional: boolean,
+ Declarations: {Declaration},
+}, {
+ Identifier: Token,
}>
export type Options = {
@@ -111,7 +153,7 @@ export type Options = {
export type Duplicate = {
Type: string,
- Identifier: Lexer.Token,
+ Identifier: Token,
Declaration: Declaration
}
@@ -122,12 +164,12 @@ export type Parser = {
Scope: string?,
Types: {[string]: Duplicate},
Events: {[string]: Duplicate},
- LookAhead: Lexer.Token?,
+ LookAhead: Token?,
Parse: (self: Parser, Source: string) -> (Body, Options),
- Consume: (self: Parser, Type: Lexer.Types) -> Lexer.Token,
- ConsumeAny: (self: Parser, Types: {Lexer.Types}) -> Lexer.Token,
- GetSafeLookAhead: (self: Parser) -> Lexer.Token,
+ Consume: (self: Parser, Type: Lexer.Types) -> Token,
+ ConsumeAny: (self: Parser, Types: {Lexer.Types}) -> Token,
+ GetSafeLookAhead: (self: Parser) -> Token,
--> Parsable types
Body: (self: Parser) -> Body,
@@ -136,25 +178,36 @@ export type Parser = {
Declaration: (self: Parser) -> Declaration,
Declarations: (self: Parser) -> {Declaration},
- MapDeclaration: (self: Parser, Identifier: Lexer.Token) -> MapDeclaration,
- TypeDeclaration: (self: Parser, Identifier: Lexer.Token) -> TypeDeclaration,
- EnumDeclaration: (self: Parser, Identifier: Lexer.Token) -> EnumDeclaration,
- TupleDeclaration: (self: Parser, Identifier: Lexer.Token) -> TupleDeclaration,
- StructDeclaration: (self: Parser, Identifier: Lexer.Token) -> StructDeclaration,
- EventDeclaration: (self: Parser, Identifier: Lexer.Token) -> EventDeclaration,
- FunctionDeclaration: (self: Parser, Identifier: Lexer.Token) -> FunctionDeclaration,
- ScopeDeclaration: (self: Parser, Identifier: Lexer.Token) -> ScopeDeclaration,
-
- GetReference: (self: Parser, Reference: Lexer.Token) -> Declaration,
- GetTypeAttributes: (self: Parser, Primitive: Lexer.Token?) -> (NumberRange?, NumberRange?),
- GetDeclarationFromToken: (self: Parser, Token: Lexer.Token, Identifier: Lexer.Token) -> (Declaration, Lexer.Token?),
+ MapDeclaration: (self: Parser, Identifier: Token) -> MapDeclaration,
+ TypeDeclaration: (self: Parser, Identifier: Token) -> TypeDeclaration,
+ EnumDeclaration: (self: Parser, Identifier: Token) -> EnumDeclaration,
+ TupleDeclaration: (self: Parser, Identifier: Token) -> TupleDeclaration,
+ StructDeclaration: (self: Parser, Identifier: Token) -> StructDeclaration,
+ EventDeclaration: (self: Parser, Identifier: Token) -> EventDeclaration,
+ FunctionDeclaration: (self: Parser, Identifier: Token) -> FunctionDeclaration,
+ ScopeDeclaration: (self: Parser, Identifier: Token) -> ScopeDeclaration,
+
+ GetReference: (self: Parser, Reference: Token) -> Declaration,
+ GetTypeAttributes: (self: Parser, Primitive: Token?) -> (NumberRange?, NumberRange?),
+ GetDeclarationFromToken: (self: Parser, Token: Token, Identifier: Token) -> (Declaration, Token?),
}
-local Errors = {
- DuplicateDeclaration = 3001
+local RangePrimitives = {
+ u8 = true,
+ u16 = true,
+ u32 = true,
+ i8 = true,
+ i16 = true,
+ i32 = true,
+ f16 = true,
+ f32 = true,
+ f64 = true,
+ string = true,
+ vector = true,
+ buffer = true,
}
-local RangePrimitives = {
+local OptionalPrimitives = {
u8 = true,
u16 = true,
u32 = true,
@@ -164,9 +217,13 @@ local RangePrimitives = {
f16 = true,
f32 = true,
f64 = true,
+ boolean = true,
string = true,
vector = true,
buffer = true,
+ Color3 = true,
+ CFrame = true,
+ Instance = true,
}
local OptionsTypes: {[string]: Lexer.Types} = {
@@ -216,52 +273,63 @@ local NumberRange = {
end
}
-local function GetAttributeRange(Token: Lexer.Token, Array: boolean, Source: string): NumberRange
- local Lower, Upper;
- if Array then
- Lower, Upper = string.match(Token.Value, `^%[({Number})..({Number})]`)
- Lower = Lower or string.match(Token.Value, `^%[({Number})]`)
+local function GetTypeRange(Token: Token, Array: boolean, Source: string): NumberRange
+ local Value = Token.Value
+ local function ThrowMalformedRange(): never
+ Error.new(Error.AnalyzeInvalidRange, Source, "Malformed range")
+ :Primary(Token, "Unable to parse range")
+ :Emit()
+ error("Unable to parse range")
+ end
+
+ --> Exact size array/range
+ local Single = Array
+ and string.match(Value, "%[(%d+)%]")
+ or string.match(Value, "%((%d+)%)")
+
+ if Single then
+ local Number: number = tonumber(Single) :: number
+ if not Number then
+ ThrowMalformedRange()
+ end
- local Size = tonumber(Lower) or -1
- if Size < 0 then
+ if Array and Number < 0 then
Error.new(Error.AnalyzeInvalidRange, Source, "Invalid array size")
:Primary(Token, "Array cannot be smaller than 0 elements")
:Emit()
end
- if not Upper then
- return NumberRange.new(Size) :: NumberRange
- end
- else
- Lower, Upper = string.match(Token.Value, `^%(({Number})..({Number})%)`)
- if not Upper then
- Lower = string.match(Token.Value, `^%(({Number})%)`)
- end
+ return NumberRange.new(Number, Number)
end
- if not Lower then
- error(`Unexpected error while trying to parse range.`)
- end
+ --> Variable size array/range
+ local Lower = Array and
+ string.match(Value, "%[(%d+)")
+ or string.match(Value, "%((%d+)")
- local Min = tonumber(Lower) or 0
- local Max;
+ local Upper = Array
+ and string.match(Value, "(%d+)%]")
+ or string.match(Value, "(%d+)%)")
- --[[if Min < 0 then
- Error.new(Error.AnalyzeInvalidRange, Source, "Invalid range")
- :Primary(Token, "Minimum cannot be negative")
- :Emit()
- end]]
+ if Lower and Upper then
+ local Minimum: number = tonumber(Lower) :: number
+ local Maximum: number = tonumber(Upper) :: number
+
+ if not Minimum or not Maximum then
+ ThrowMalformedRange()
+ end
- if Upper then
- Max = tonumber(Upper) or 0
- if Min >= Max then
+ if Minimum >= Maximum then
Error.new(Error.AnalyzeInvalidRange, Source, "Invalid range")
:Primary(Token, "Maximum must be greater than minimum")
:Emit()
end
+
+ return NumberRange.new(Minimum, Maximum)
end
- return NumberRange.new(Min, Max)
+ ThrowMalformedRange()
+ return NumberRange.new(0, 0)
end
local Parser = {}
@@ -295,7 +363,7 @@ function Parser.Parse(self: Parser, Source: string): (Body, Options)
return Body, Options
end
-function Parser.Consume(self: Parser, Type: string): Lexer.Token
+function Parser.Consume(self: Parser, Type: string): Token
local Token = self.LookAhead
if not Token then
Error.new(Error.ParserUnexpectedEndOfFile, self.Source, "Unexpected end of file")
@@ -318,7 +386,7 @@ function Parser.Consume(self: Parser, Type: string): Lexer.Token
return Token
end
-function Parser.ConsumeAny(self: Parser, Types: {Lexer.Types}): Lexer.Token
+function Parser.ConsumeAny(self: Parser, Types: {Lexer.Types}): Token
local Token = self.LookAhead
if not Token then
Error.new(Error.ParserUnexpectedEndOfFile, self.Source, "Unexpected end of file")
@@ -341,7 +409,7 @@ function Parser.ConsumeAny(self: Parser, Types: {Lexer.Types}): Lexer.Token
return Token
end
-function Parser.GetSafeLookAhead(self: Parser): Lexer.Token
+function Parser.GetSafeLookAhead(self: Parser): Token
if self.LookAhead then
return self.LookAhead
end
@@ -357,7 +425,8 @@ end
function Parser.Body(self: Parser): Body
return {
Type = "Body",
- Value = self:Declarations()
+ Value = self:Declarations(),
+ Tokens = {}
}
end
@@ -401,18 +470,18 @@ function Parser.Declarations(self: Parser): {Declaration}
end
function Parser.Declaration(self: Parser): Declaration
- local Keyword = self:Consume("Keyword")
+ local Keyword = self:Consume("Keyword").Value
local Identifier = self:Consume("Identifier")
self:Consume("Assign")
--> Handle duplicate declarations
- local Type = KeywordTypes[Keyword.Value]
+ local Type = KeywordTypes[Keyword]
local Duplicates: {[string]: Duplicate} = self[Type]
local ScopedIdentifier = `{self.Scope and `{self.Scope}.` or ""}{Identifier.Value}`
local Duplicate = Duplicates[ScopedIdentifier]
if Duplicate then
- Error.new(Errors.DuplicateDeclaration, self.Source, `Duplicate {Keyword.Value} "{Identifier.Value}"`)
+ Error.new(Error.AnalyzeDuplicateDeclaration, self.Source, `Duplicate {Keyword} "{Identifier.Value}"`)
:Secondary(Duplicate.Identifier, "Previously delcared here")
:Primary(Identifier, "Duplicate declaration here")
:Emit()
@@ -420,32 +489,42 @@ function Parser.Declaration(self: Parser): Declaration
--> Parse declaration
local Declaration: Declaration?;
- if Keyword.Value == "map" then
+ if Keyword == "map" then
Declaration = self:MapDeclaration(Identifier)
- elseif Keyword.Value == "type" then
+ elseif Keyword == "type" then
Declaration = self:TypeDeclaration(Identifier)
- elseif Keyword.Value == "enum" then
+ elseif Keyword == "enum" then
Declaration = self:EnumDeclaration(Identifier)
- elseif Keyword.Value == "struct" then
+ elseif Keyword == "struct" then
Declaration = self:StructDeclaration(Identifier)
- elseif Keyword.Value == "event" then
+ elseif Keyword == "event" then
Declaration = self:EventDeclaration(Identifier)
- elseif Keyword.Value == "function" then
+ elseif Keyword == "function" then
Declaration = self:FunctionDeclaration(Identifier)
- elseif Keyword.Value == "scope" then
+ elseif Keyword == "scope" then
Declaration = self:ScopeDeclaration(Identifier)
end
if not Declaration then
- error(`{Keyword.Value} has no declaration handler.`)
+ error(`{Keyword} has no declaration handler.`)
end
if
Duplicates == self.Types
and (self:GetSafeLookAhead().Type == "Optional")
then
- self:Consume("Optional")
+ local Optional = self:Consume("Optional")
+ if Declaration.Type == "TypeDeclaration" then
+ local Primitive = (Declaration :: TypeDeclaration).Value.Primitive
+ if Primitive == "unknown" then
+ Error.new(Error.AnalyzeInvalidOptionalType, self.Source, `Invalid optional type`)
+ :Primary(Optional, `"unknown" cannot be optional`)
+ :Emit()
+ end
+ end
+
Declaration.Value.Optional = true
+ Declaration.Tokens.Optional = Optional
end
--> Save declaration to duplicates
@@ -458,10 +537,10 @@ function Parser.Declaration(self: Parser): Declaration
return Declaration
end
-function Parser.TypeDeclaration(self: Parser, Identifier: Lexer.Token): TypeDeclaration
+function Parser.TypeDeclaration(self: Parser, Identifier: Token): TypeDeclaration
local LookAhead = self:GetSafeLookAhead()
- local Primitive: Lexer.Token;
+ local Primitive: Token;
local Declaration: TypeDeclaration;
if LookAhead.Type == "Identifier" then
@@ -475,6 +554,8 @@ function Parser.TypeDeclaration(self: Parser, Identifier: Lexer.Token): TypeDecl
self:Consume("Identifier")
Declaration = Table.DeepClone(Reference)
Declaration.Value.Identifier = Identifier.Value
+ Declaration.Tokens.Identifier = Identifier
+ Declaration.Tokens.Value = LookAhead
else
Primitive = self:Consume("Primitive")
Declaration = {
@@ -484,6 +565,10 @@ function Parser.TypeDeclaration(self: Parser, Identifier: Lexer.Token): TypeDecl
Identifier = Identifier.Value,
Primitive = Primitive.Value,
Optional = false
+ },
+ Tokens = {
+ Identifier = Identifier,
+ Value = Primitive,
}
}
@@ -510,7 +595,7 @@ function Parser.TypeDeclaration(self: Parser, Identifier: Lexer.Token): TypeDecl
return Declaration
end
-function Parser.EnumDeclaration(self: Parser, Identifier: Lexer.Token): EnumDeclaration
+function Parser.EnumDeclaration(self: Parser, Identifier: Token): EnumDeclaration
self:Consume("OpenBrackets")
local Enums: {string} = {}
@@ -548,11 +633,14 @@ function Parser.EnumDeclaration(self: Parser, Identifier: Lexer.Token): EnumDecl
Identifier = Identifier.Value,
Enums = Enums,
Optional = false
+ },
+ Tokens = {
+ Identifier = Identifier,
}
}
end
-function Parser.MapDeclaration(self: Parser, Identifier: Lexer.Token): MapDeclaration
+function Parser.MapDeclaration(self: Parser, Identifier: Token): MapDeclaration
self:Consume("OpenCurlyBrackets")
self:Consume("OpenSquareBrackets")
@@ -625,14 +713,19 @@ function Parser.MapDeclaration(self: Parser, Identifier: Lexer.Token): MapDeclar
Array = Array,
Optional = false,
+ },
+ Tokens = {
+ Identifier = Identifier,
+ Key = Key,
+ Value = Value,
}
}
end
-function Parser.StructDeclaration(self: Parser, Identifier: Lexer.Token): StructDeclaration
+function Parser.StructDeclaration(self: Parser, Identifier: Token): StructDeclaration
self:Consume("OpenCurlyBrackets")
- local Keys: {[string]: Lexer.Token} = {}
+ local Keys: {[string]: Token} = {}
local Fields = {}
while (self.LookAhead) do
@@ -673,11 +766,14 @@ function Parser.StructDeclaration(self: Parser, Identifier: Lexer.Token): Struct
Identifier = Identifier.Value,
Fields = Fields,
Optional = false,
+ },
+ Tokens = {
+ Identifier = Identifier,
}
}
end
-function Parser.TupleDeclaration(self: Parser, Identifier: Lexer.Token): TupleDeclaration
+function Parser.TupleDeclaration(self: Parser, Identifier: Token): TupleDeclaration
self:Consume("OpenSquareBrackets")
local Values: {Declaration} = {}
@@ -706,11 +802,14 @@ function Parser.TupleDeclaration(self: Parser, Identifier: Lexer.Token): TupleDe
Identifier = Identifier.Value,
Values = Values,
Optional = false
+ },
+ Tokens = {
+ Identifier = Identifier
}
}
end
-function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDeclaration
+function Parser.EventDeclaration(self: Parser, Identifier: Token): EventDeclaration
self:Consume("OpenCurlyBrackets")
if ReservedMembers[Identifier.Value] then
@@ -729,6 +828,10 @@ function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDe
Call = "SingleSync",
Data = nil :: any,
Optional = false
+ },
+ Tokens = {
+ Identifier = Identifier,
+ Fields = {}
}
}
@@ -740,11 +843,12 @@ function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDe
:Emit()
end
- local Value: unknown;
+ local Value: any;
local Assign = self:Consume("Assign")
+ local Token = self:GetSafeLookAhead()
if Entry.Key ~= "Data" then
- local Token = self:Consume(Entry.Type)
+ Token = self:Consume(Entry.Type)
if not table.find(Entry.Values, Token.Value) then
Error.new(Error.ParserUnexpectedToken, self.Source, `Unexpected token`)
:Primary(Token, `Expected one of "{table.concat(Entry.Values, "\" or \"")}", found "{Token.Value}"`)
@@ -753,8 +857,7 @@ function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDe
Value = Token.Value
else
- local Token = self.LookAhead
- if not Token then
+ if not self.LookAhead then
Error.new(Error.ParserExpectedExtraToken, self.Source, `Expected a token`)
:Primary(Assign, `Expected a value to follow after assignment`)
:Emit()
@@ -769,6 +872,10 @@ function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDe
end
Event.Value[Entry.Key] = Value
+ table.insert(Event.Tokens, {
+ Field = Key,
+ Value = Token
+ })
if Index ~= #EventStructure then
self:Consume("Comma")
@@ -780,7 +887,7 @@ function Parser.EventDeclaration(self: Parser, Identifier: Lexer.Token): EventDe
return Event
end
-function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): FunctionDeclaration
+function Parser.FunctionDeclaration(self: Parser, Identifier: Token): FunctionDeclaration
self:Consume("OpenCurlyBrackets")
if ReservedMembers[Identifier.Value] then
@@ -798,6 +905,10 @@ function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): Func
Yield = "Coroutine",
Data = nil :: any,
Return = nil :: any
+ },
+ Tokens = {
+ Identifier = Identifier,
+ Fields = {}
}
}
@@ -811,9 +922,10 @@ function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): Func
local Value: unknown;
local Assign = self:Consume("Assign")
+ local Token = self:GetSafeLookAhead()
if Entry.Key ~= "Data" and Entry.Key ~= "Return" then
- local Token = self:Consume(Entry.Type)
+ Token = self:Consume(Entry.Type)
if not table.find(Entry.Values, Token.Value) then
Error.new(Error.ParserUnexpectedToken, self.Source, `Unexpected token`)
:Primary(Token, `Expected one of "{table.concat(Entry.Values, "\" or \"")}", found "{Token.Value}"`)
@@ -822,8 +934,7 @@ function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): Func
Value = Token.Value
else
- local Token = self.LookAhead
- if not Token then
+ if not self.LookAhead then
Error.new(Error.ParserExpectedExtraToken, self.Source, `Expected a token`)
:Primary(Assign, `Expected a value to follow after assignment`)
:Emit()
@@ -838,6 +949,10 @@ function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): Func
end
Function.Value[Entry.Key] = Value
+ table.insert(Function.Tokens, {
+ Field = Key,
+ Value = Token
+ })
if Index ~= #FunctionStructure then
self:Consume("Comma")
@@ -849,7 +964,7 @@ function Parser.FunctionDeclaration(self: Parser, Identifier: Lexer.Token): Func
return Function
end
-function Parser.ScopeDeclaration(self: Parser, Identifier: Lexer.Token): ScopeDeclaration
+function Parser.ScopeDeclaration(self: Parser, Identifier: Token): ScopeDeclaration
if self.Scope then
Error.new(Error.AnalyzeNestedScope, self.Source, "Nested scope")
:Primary(Identifier, `Blink doesn't currently support declaring scopes within scopes`)
@@ -870,6 +985,9 @@ function Parser.ScopeDeclaration(self: Parser, Identifier: Lexer.Token): ScopeDe
Identifier = Scope,
Optional = false,
Declarations = Declarations
+ },
+ Tokens = {
+ Identifier = Identifier
}
}
@@ -890,7 +1008,7 @@ function Parser.ScopeDeclaration(self: Parser, Identifier: Lexer.Token): ScopeDe
return Declaration
end
-function Parser.GetReference(self: Parser, Token: Lexer.Token): Declaration
+function Parser.GetReference(self: Parser, Token: Token): Declaration
local Identifier = `{self.Scope and `{self.Scope}.` or ""}{Token.Value}`
local Reference = self.Types[Identifier] or self.Types[Token.Value]
if not Reference then
@@ -902,7 +1020,7 @@ function Parser.GetReference(self: Parser, Token: Lexer.Token): Declaration
return Reference.Declaration
end
-function Parser.GetTypeAttributes(self: Parser, Primitive: Lexer.Token?): (NumberRange?, NumberRange?)
+function Parser.GetTypeAttributes(self: Parser, Primitive: Token?): (NumberRange?, NumberRange?)
local Array, Range;
for _, Expected in {"Range", "Array"} do
local LookAhead = self.LookAhead
@@ -926,9 +1044,9 @@ function Parser.GetTypeAttributes(self: Parser, Primitive: Lexer.Token?): (Numbe
:Emit()
end
- Range = GetAttributeRange(LookAhead, false, self.Source)
+ Range = GetTypeRange(LookAhead, false, self.Source)
elseif Type == "Array" then
- Array = GetAttributeRange(LookAhead, true, self.Source)
+ Array = GetTypeRange(LookAhead, true, self.Source)
end
self:Consume(Type)
@@ -937,7 +1055,7 @@ function Parser.GetTypeAttributes(self: Parser, Primitive: Lexer.Token?): (Numbe
return Array, Range
end
-function Parser.GetDeclarationFromToken(self: Parser, Token: Lexer.Token, Identifier: Lexer.Token): (Declaration, Lexer.Token?)
+function Parser.GetDeclarationFromToken(self: Parser, Token: Token, Identifier: Token): (Declaration, Token?)
local Declaration: Declaration;
if Token.Type == "OpenCurlyBrackets" then
local NextToken = self.Lexer:GetNextToken(true)
@@ -951,8 +1069,8 @@ function Parser.GetDeclarationFromToken(self: Parser, Token: Lexer.Token, Identi
elseif Token.Type == "Primitive" then
Declaration = self:TypeDeclaration(Identifier)
elseif Token.Type == "Identifier" then
- local Type = self:Consume("Identifier")
- local Reference = self:GetReference(Type)
+ self:Consume("Identifier")
+ local Reference = self:GetReference(Token)
local Array, Range = self:GetTypeAttributes()
Declaration = {
@@ -965,6 +1083,9 @@ function Parser.GetDeclarationFromToken(self: Parser, Token: Lexer.Token, Identi
Array = Array,
Range = Range,
Optional = false
+ },
+ Tokens = {
+ Identifier = Identifier
}
}
end
@@ -979,6 +1100,16 @@ function Parser.GetDeclarationFromToken(self: Parser, Token: Lexer.Token, Identi
if self:GetSafeLookAhead().Type == "Optional" then
Optional = self:Consume("Optional")
Declaration.Value.Optional = true
+
+ if Token.Type == "Identifier" then
+ local Type: TypeDeclaration = self:GetReference(Token) :: TypeDeclaration
+ if Type.Value.Primitive == "unknown" then
+ Error.new(Error.AnalyzeInvalidOptionalType, self.Source, `Invalid optional type`)
+ :Secondary(Type.Tokens.Value, "Declared here")
+ :Primary(Optional, `{Type.Value.Identifier} is of type "unknown", "unknown" cannot be optional`)
+ :Emit()
+ end
+ end
end
return Declaration, Optional
diff --git a/src/Settings.luau b/src/Settings.luau
new file mode 100644
index 0000000..e919352
--- /dev/null
+++ b/src/Settings.luau
@@ -0,0 +1,34 @@
+return {
+ Keywords = {
+ map = true,
+ type = true,
+ enum = true,
+ struct = true,
+
+ event = true,
+ ["function"] = true,
+
+ scope = true,
+ option = true,
+ },
+
+ Primtives = {
+ u8 = true,
+ u16 = true,
+ u32 = true,
+ i8 = true,
+ i16 = true,
+ i32 = true,
+ f16 = true,
+ f32 = true,
+ f64 = true,
+ boolean = true,
+ string = true,
+ vector = true,
+ buffer = true,
+ Color3 = true,
+ CFrame = true,
+ Instance = true,
+ unknown = true
+ }
+}
\ No newline at end of file
diff --git a/test/Sources/Conflicts.txt b/test/Sources/Conflicts.txt
index 075dfff..18c1f52 100644
--- a/test/Sources/Conflicts.txt
+++ b/test/Sources/Conflicts.txt
@@ -3,13 +3,13 @@ option ClientOutput = "../Network/ClientConflict.luau"
option ServerOutput = "../Network/ServerConflict.luau"
type Name = u8?
-type Unknown = i8[1]
+type Variable = unknown
event Name = {
From = Server,
Type = Reliable,
Call = SingleSync,
- Data = u8
+ Data = Variable
}
scope s = {
diff --git a/test/Sources/Test.txt b/test/Sources/Test.txt
index 23d0362..966d155 100644
--- a/test/Sources/Test.txt
+++ b/test/Sources/Test.txt
@@ -31,6 +31,13 @@ struct MapStruct = {
Map = {[string] = u8}
}
+event Unknown = {
+ From = Server,
+ Type = Reliable,
+ Call = SingleSync,
+ Data = unknown
+}
+
event MapEvent = {
From = Server,
Type = Reliable,
diff --git a/test/Test.luau b/test/Test.luau
index b8b546d..88f477f 100644
--- a/test/Test.luau
+++ b/test/Test.luau
@@ -403,6 +403,8 @@ FireAndExpect("Unreliable Server", Server.UnreliableServer, Client.UnreliableSer
FireAndExpect("Reliable Client", Client.ReliableClient, Server.ReliableClient, 1)
FireAndExpect("Unreliable Client", Client.UnreliableClient, Server.UnreliableClient, 1)
+FireAndExpect("Unknown", Server.Unknown, Client.Unknown, newproxy())
+
FireAndExpect("Map", Server.MapEvent, Client.MapEvent, {["string"] = 1})
FireAndExpect("Map Struct", Server.MapStructEvent, Client.MapStructEvent, {Map = {string = 1}})
FireAndExpect("Map Complex", Server.MapComplexEvent, Client.MapComplexEvent, {string = table.create(8, 1)})