Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .luarc.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@

"hint.semicolon": "Disable",

"runtime.plugin": "common/lib/luaLsPlugin.lua",

// to support mixin union types where a table is both types rather than just one
"type.weakUnionCheck": true,

"workspace.checkThirdParty": false,
"workspace.ignoreDir": [
"client/lib/rich_presence/",
Expand All @@ -61,7 +66,9 @@
"common/lib/socket.lua",
"common/lib/dkjson.lua",
"common/lib/csprng.lua",
"common/lib/jprof/MessagePack.lua"
"common/lib/jprof/MessagePack.lua",
"common/lib/import.lua",
"common/lib/luaLsPlugin.lua"
],
"workspace.library": [
".vscode/love2d-12/library"
Expand Down
1 change: 1 addition & 0 deletions client/src/ui/BoolSelector.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ local DebugSettings = require("client.src.debug.DebugSettings")

--- A BoolSelector is a UIElement that shows if a setting is on or off and lets you toggle it.
---@class BoolSelector : UiElement
---@operator call(BoolSelectorOptions): BoolSelector
---@field value boolean
---@field vertical boolean
---@field circleRadius number
Expand Down
145 changes: 89 additions & 56 deletions client/src/ui/init.lua
Original file line number Diff line number Diff line change
@@ -1,62 +1,95 @@
local PATH = (...):gsub('%.init$', '')
-- import is getting "live replaced" for intellisense via the lua LS plugin so the editor incorrectly detects it as unused-local
-- but without lua LS it is a real function that manages the relative require
---@diagnostic disable-next-line: unused-local
local import = require("common.lib.import")

--[[
tag each with
---@source relative path
that way "Go to source" on an import of ui elsewhere will lead to the respective source instead of this file
the "./" is assumed given for relative paths but it's still a path so adding the file extension is necessary
when addressing files in subdirectories of ui use forward slashes as the path separator
https://luals.github.io/wiki/annotations/#source

Intellisense for constructors that have their constructor annotated usually works fine if you type
ui.UiElement({})
and then navigate back into the {} and hit Ctrl+Space for suggestions

"Go to source" on functions will work after annotating either
---@operator call(argType): classname
or
---@overload fun(options: argType): classname
on the class itself as luaLS only then correctly infers the return from the constructor
]]


local ui = {
---@see BoolSelector
---@type fun(options: BoolSelectorOptions): BoolSelector
BoolSelector = require(PATH .. ".BoolSelector"),
---@see Button
---@type fun(options: ButtonOptions): Button
Button = require(PATH .. ".Button"),
ButtonGroup = require(PATH .. ".ButtonGroup"),
Carousel = require(PATH .. ".Carousel"),
---@see ChangeInputButton
---@type fun(options: ChangeInputButtonOptions): ChangeInputButton
ChangeInputButton = require(PATH .. ".ChangeInputButton"),
Focusable = require(PATH .. ".Focusable"),
FocusDirector = require(PATH .. ".FocusDirector"),
Grid = require(PATH .. ".Grid"),
GridCursor = require(PATH .. ".GridCursor"),
---@see ImageButton
---@type fun(options: ImageButtonOptions): ImageButton
ImageButton = require(PATH .. ".ImageButton"),
ImageContainer = require(PATH .. ".ImageContainer"),
InputField = require(PATH .. ".InputField"),
KeyBindingMenuItem = require(PATH .. ".KeyBindingMenuItem"),
---@see Label
---@type fun(options: LabelOptions): Label
Label = require(PATH .. ".Label"),
Leaderboard = require(PATH .. ".Leaderboard"),
---@see LevelSlider
---@type fun(options: SliderOptions): LevelSlider
LevelSlider = require(PATH .. ".LevelSlider"),
Menu = require(PATH .. ".Menu"),
MenuItem = require(PATH .. ".MenuItem"),
MultiPlayerSelectionWrapper = require(PATH .. ".MultiPlayerSelectionWrapper"),
PagedUniGrid = require(PATH .. ".PagedUniGrid"),
PanelCarousel = require(PATH .. ".PanelCarousel"),
---@see PixelFontLabel
---@type fun(options: PixelFontLabelOptions): PixelFontLabel
PixelFontLabel = require(PATH .. ".PixelFontLabel"),
---@see ScrollContainer
---@type fun(options: ScrollContainerOptions): ScrollContainer
ScrollContainer = require(PATH .. ".ScrollContainer"),
ScrollText = require(PATH .. ".ScrollText"),
---@see Slider
---@type fun(options: SliderOptions): Slider
Slider = require(PATH .. ".Slider"),
SliderMenuItem = require(PATH .. ".SliderMenuItem"),
---@source BoolSelector.lua
BoolSelector = import("./BoolSelector"),
---@source Button.lua
Button = import("./Button"),
---@source ButtonGroup.lua
ButtonGroup = import("./ButtonGroup"),
---@source Carousel.lua
Carousel = import("./Carousel"),
---@source ChangeInputButton.lua
ChangeInputButton = import("./ChangeInputButton"),
---@source Focusable.lua
Focusable = import("./Focusable"),
---@source FocusDirector.lua
FocusDirector = import("./FocusDirector"),
---@source Grid.lua
Grid = import("./Grid"),
---@source GridCursor.lua
GridCursor = import("./GridCursor"),
---@source ImageButton.lua
ImageButton = import("./ImageButton"),
---@source ImageContainer.lua
ImageContainer = import("./ImageContainer"),
---@source InputField.lua
InputField = import("./InputField"),
---@source KeyBindingMenuItem.lua
KeyBindingMenuItem = import("./KeyBindingMenuItem"),
---@source Label.lua
Label = import("./Label"),
---@source Leaderboard.lua
Leaderboard = import("./Leaderboard"),
---@source LevelSlider.lua
LevelSlider = import("./LevelSlider"),
---@source Menu.lua
Menu = import("./Menu"),
---@source MenuItem.lua
MenuItem = import("./MenuItem"),
---@source MultiPlayerSelectionWrapper.lua
MultiPlayerSelectionWrapper = import("./MultiPlayerSelectionWrapper"),
---@source PagedUniGrid.lua
PagedUniGrid = import("./PagedUniGrid"),
---@source PanelCarousel.lua
PanelCarousel = import("./PanelCarousel"),
---@source PixelFontLabel.lua
PixelFontLabel = import("./PixelFontLabel"),
---@source ScrollContainer.lua
ScrollContainer = import("./ScrollContainer"),
---@source ScrollText.lua
ScrollText = import("./ScrollText"),
---@source Slider.lua
Slider = import("./Slider"),
---@source SliderMenuItem.lua
SliderMenuItem = import("./SliderMenuItem"),
---@source StackElement.lua
StackElement = require(PATH .. ".StackElement"),
StackPanel = require(PATH .. ".StackPanel"),
StageCarousel = require(PATH .. ".StageCarousel"),
Stepper = require(PATH .. ".Stepper"),
---@see TextButton
---@type fun(options: TextButtonOptions): TextButton
TextButton = require(PATH .. ".TextButton"),
---@see UiElement
---@type fun(options:UiElementOptions): UiElement
UiElement = require(PATH .. ".UIElement"),
ValueLabel = require(PATH .. ".ValueLabel"),
StackElement = import("./StackElement"),
---@source StackPanel.lua
StackPanel = import("./StackPanel"),
---@source StageCarousel.lua
StageCarousel = import("./StageCarousel"),
---@source Stepper.lua
Stepper = import("./Stepper"),
---@source TextButton.lua
TextButton = import("./TextButton"),
---@source UiElement.lua
UiElement = import("./UIElement"),
---@source ValueLabel.lua
ValueLabel = import("./ValueLabel"),
}

return ui
68 changes: 68 additions & 0 deletions common/lib/import.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
--[[
A library to provide a relative require.
Makes sense to use wherever we have grouped files that assuredly only ever move together.
Otherwise require is probably still better.

MIT License

Copyright (c) 2023 Justin van der Leij

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

local function extractPathComponents(path)
local components = {}
for component in path:gmatch("[^/]+") do
table.insert(components, component)
end

return components
end

local import = function(path)
local callerPath = debug.getinfo(2, "S").source:sub(2)

local pathStack = {}

if (path:sub(1, 1) == ".") then
local components = extractPathComponents(callerPath)

for i = 1, #components - 1 do
pathStack[i] = components[i]
end
end

local components = extractPathComponents(path)

for _, component in ipairs(components) do
if (component == ".") then
-- Skip
elseif (component == "..") then
table.remove(pathStack, #pathStack)
else
table.insert(pathStack, component)
end
end

local out = table.concat(pathStack, ".")

return require(out)
end

return import
83 changes: 83 additions & 0 deletions common/lib/luaLsPlugin.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
--[[
A plugin for the language server to resolve the import libraries requires
Intellisense becomes available for the returned types despite them technically being just a lua function call

MIT License

Copyright (c) 2024 Elmārs Āboliņš, including code from Justin van der Leij

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

local workspace = require "workspace"

local function extractPathComponents(path)
local components = {}
for component in path:gmatch("[^/]+") do
table.insert(components, component)
end

return components
end

local import = function(path, fileUri)
local callerPath = fileUri

local pathStack = {}

if (path:sub(1, 1) == ".") then
local components = extractPathComponents(callerPath)

for i = 1, #components - 1 do
pathStack[i] = components[i]
end
end

local components = extractPathComponents(path)

for _, component in ipairs(components) do
if (component == ".") then
-- Skip
elseif (component == "..") then
table.remove(pathStack, #pathStack)
else
table.insert(pathStack, component)
end
end

local out = table.concat(pathStack, ".")

return "require(\""..out.."\")"
end

function OnSetText(uri, text)
local diffs = {}

local transformedUri = uri:sub(#workspace.rootUri+2)

for startPos, path, finish in text:gmatch '()import%(([^%(%)]+)%)()' do
diffs[#diffs+1] = {
start = startPos,
finish = finish - 1,
text = import(path:gsub('\"',""), transformedUri),
}
end

return diffs
end