mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-25 04:54:07 +00:00
- Updated logging statements in various functions across the ApiContext, ServeSession, and Reconciler modules to use `Log.debug` instead of `Log.trace`. - This change aims to reduce the verbosity of logs and improve performance by limiting the amount of logged information during execution, while still retaining essential timing information for debugging purposes.
452 lines
14 KiB
Lua
452 lines
14 KiB
Lua
local Rojo = script:FindFirstAncestor("Rojo")
|
|
local Plugin = Rojo.Plugin
|
|
local Packages = Rojo.Packages
|
|
|
|
local Roact = require(Packages.Roact)
|
|
local Log = require(Packages.Log)
|
|
local Highlighter = require(Packages.Highlighter)
|
|
Highlighter.matchStudioSettings()
|
|
local StringDiff = require(script:FindFirstChild("StringDiff"))
|
|
|
|
local Timer = require(Plugin.Timer)
|
|
local Theme = require(Plugin.App.Theme)
|
|
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
|
|
|
|
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
|
local VirtualScroller = require(Plugin.App.Components.VirtualScroller)
|
|
|
|
local e = Roact.createElement
|
|
|
|
local StringDiffVisualizer = Roact.Component:extend("StringDiffVisualizer")
|
|
|
|
function StringDiffVisualizer:init()
|
|
self.scriptBackground, self.setScriptBackground = Roact.createBinding(Color3.fromRGB(0, 0, 0))
|
|
self.updateEvent = Instance.new("BindableEvent")
|
|
self.lineHeight, self.setLineHeight = Roact.createBinding(15)
|
|
self.canvasPosition, self.setCanvasPosition = Roact.createBinding(Vector2.zero)
|
|
self.windowWidth, self.setWindowWidth = Roact.createBinding(math.huge)
|
|
|
|
-- Ensure that the script background is up to date with the current theme
|
|
self.themeChangedConnection = settings().Studio.ThemeChanged:Connect(function()
|
|
-- Delay to allow Highlighter to process the theme change first
|
|
task.delay(1 / 20, function()
|
|
self:updateScriptBackground()
|
|
self:updateDiffs()
|
|
-- Rerender the virtual list elements
|
|
self.updateEvent:Fire()
|
|
end)
|
|
end)
|
|
|
|
self:updateScriptBackground()
|
|
self:updateDiffs()
|
|
end
|
|
|
|
function StringDiffVisualizer:willUnmount()
|
|
self.themeChangedConnection:Disconnect()
|
|
self.updateEvent:Destroy()
|
|
end
|
|
|
|
function StringDiffVisualizer:updateScriptBackground()
|
|
local backgroundColor = Highlighter.getTokenColor("background")
|
|
if backgroundColor ~= self.scriptBackground:getValue() then
|
|
self.setScriptBackground(backgroundColor)
|
|
end
|
|
end
|
|
|
|
function StringDiffVisualizer:didUpdate(previousProps)
|
|
if
|
|
previousProps.currentString ~= self.props.currentString
|
|
or previousProps.incomingString ~= self.props.incomingString
|
|
then
|
|
self:updateDiffs()
|
|
end
|
|
end
|
|
|
|
function StringDiffVisualizer:updateDiffs()
|
|
Timer.start("StringDiffVisualizer:updateDiffs")
|
|
local currentString, incomingString = self.props.currentString, self.props.incomingString
|
|
|
|
-- Diff the two texts
|
|
local startClock = os.clock()
|
|
local diffs =
|
|
StringDiff.findDiffs((string.gsub(currentString, "\t", " ")), (string.gsub(incomingString, "\t", " ")))
|
|
local stopClock = os.clock()
|
|
|
|
Log.debug(
|
|
"Diffing {} byte and {} byte strings took {} microseconds and found {} diff sections",
|
|
#currentString,
|
|
#incomingString,
|
|
math.round((stopClock - startClock) * 1000 * 1000),
|
|
#diffs
|
|
)
|
|
|
|
-- Build the rich text lines
|
|
local currentRichTextLines = Highlighter.buildRichTextLines({
|
|
src = currentString,
|
|
})
|
|
local incomingRichTextLines = Highlighter.buildRichTextLines({
|
|
src = incomingString,
|
|
})
|
|
|
|
local maxLines = math.max(#currentRichTextLines, #incomingRichTextLines)
|
|
|
|
-- Find the diff locations
|
|
local currentDiffs, incomingDiffs = {}, {}
|
|
local firstDiffLineNum = 0
|
|
|
|
local currentLineNum, incomingLineNum = 1, 1
|
|
local currentIdx, incomingIdx = 1, 1
|
|
for _, diff in diffs do
|
|
local actionType, text = diff.actionType, diff.value
|
|
local lineCount = select(2, string.gsub(text, "\n", "\n"))
|
|
local lines = string.split(text, "\n")
|
|
|
|
if actionType == StringDiff.ActionTypes.Equal then
|
|
if lineCount > 0 then
|
|
-- Jump cursor ahead to last line
|
|
currentLineNum += lineCount
|
|
incomingLineNum += lineCount
|
|
currentIdx = #lines[#lines]
|
|
incomingIdx = #lines[#lines]
|
|
else
|
|
-- Move along this line
|
|
currentIdx += #text
|
|
incomingIdx += #text
|
|
end
|
|
|
|
continue
|
|
end
|
|
|
|
if actionType == StringDiff.ActionTypes.Insert then
|
|
if firstDiffLineNum == 0 then
|
|
firstDiffLineNum = incomingLineNum
|
|
end
|
|
|
|
for i, lineText in lines do
|
|
if i > 1 then
|
|
-- Move to next line
|
|
incomingLineNum += 1
|
|
incomingIdx = 0
|
|
end
|
|
if not incomingDiffs[incomingLineNum] then
|
|
incomingDiffs[incomingLineNum] = {}
|
|
end
|
|
-- Mark these characters on this line
|
|
table.insert(incomingDiffs[incomingLineNum], {
|
|
start = incomingIdx,
|
|
stop = incomingIdx + #lineText,
|
|
})
|
|
incomingIdx += #lineText
|
|
end
|
|
elseif actionType == StringDiff.ActionTypes.Delete then
|
|
if firstDiffLineNum == 0 then
|
|
firstDiffLineNum = currentLineNum
|
|
end
|
|
|
|
for i, lineText in lines do
|
|
if i > 1 then
|
|
-- Move to next line
|
|
currentLineNum += 1
|
|
currentIdx = 0
|
|
end
|
|
if not currentDiffs[currentLineNum] then
|
|
currentDiffs[currentLineNum] = {}
|
|
end
|
|
-- Mark these characters on this line
|
|
table.insert(currentDiffs[currentLineNum], {
|
|
start = currentIdx,
|
|
stop = currentIdx + #lineText,
|
|
})
|
|
currentIdx += #lineText
|
|
end
|
|
else
|
|
Log.warn("Unknown diff action: {} {}", actionType, text)
|
|
end
|
|
end
|
|
|
|
Timer.stop()
|
|
|
|
self:setState({
|
|
maxLines = maxLines,
|
|
currentRichTextLines = currentRichTextLines,
|
|
incomingRichTextLines = incomingRichTextLines,
|
|
currentDiffs = currentDiffs,
|
|
incomingDiffs = incomingDiffs,
|
|
})
|
|
|
|
-- Scroll to the first diff line
|
|
task.defer(self.setCanvasPosition, Vector2.new(0, math.max(0, (firstDiffLineNum - 4) * 16)))
|
|
end
|
|
|
|
function StringDiffVisualizer:render()
|
|
local currentDiffs, incomingDiffs = self.state.currentDiffs, self.state.incomingDiffs
|
|
local currentRichTextLines, incomingRichTextLines =
|
|
self.state.currentRichTextLines, self.state.incomingRichTextLines
|
|
local maxLines = self.state.maxLines
|
|
|
|
return Theme.with(function(theme)
|
|
self.setLineHeight(theme.TextSize.Code)
|
|
|
|
-- Calculate the width of the canvas
|
|
-- (One line at a time to avoid the char limit of getTextBoundsAsync)
|
|
local canvasWidth = 0
|
|
for i = 1, maxLines do
|
|
local currentLine = currentRichTextLines[i]
|
|
if currentLine and string.find(currentLine, "%S") then
|
|
local bounds = getTextBoundsAsync(currentLine, theme.Font.Code, theme.TextSize.Code, math.huge, true)
|
|
if bounds.X > canvasWidth then
|
|
canvasWidth = bounds.X
|
|
end
|
|
end
|
|
local incomingLine = incomingRichTextLines[i]
|
|
if incomingLine and string.find(incomingLine, "%S") then
|
|
local bounds = getTextBoundsAsync(incomingLine, theme.Font.Code, theme.TextSize.Code, math.huge, true)
|
|
if bounds.X > canvasWidth then
|
|
canvasWidth = bounds.X
|
|
end
|
|
end
|
|
end
|
|
|
|
local lineNumberWidth =
|
|
getTextBoundsAsync(tostring(maxLines), theme.Font.Code, theme.TextSize.Body, math.huge, true).X
|
|
|
|
canvasWidth += lineNumberWidth + 12
|
|
|
|
local removalScrollMarkers = {}
|
|
local insertionScrollMarkers = {}
|
|
for lineNum in currentDiffs do
|
|
table.insert(
|
|
removalScrollMarkers,
|
|
e("Frame", {
|
|
Size = UDim2.fromScale(0.5, 1 / maxLines),
|
|
Position = UDim2.fromScale(0, (lineNum - 1) / maxLines),
|
|
BorderSizePixel = 0,
|
|
BackgroundColor3 = theme.Diff.Background.Remove,
|
|
})
|
|
)
|
|
end
|
|
for lineNum in incomingDiffs do
|
|
table.insert(
|
|
insertionScrollMarkers,
|
|
e("Frame", {
|
|
Size = UDim2.fromScale(0.5, 1 / maxLines),
|
|
Position = UDim2.fromScale(0.5, (lineNum - 1) / maxLines),
|
|
BorderSizePixel = 0,
|
|
BackgroundColor3 = theme.Diff.Background.Add,
|
|
})
|
|
)
|
|
end
|
|
|
|
return e(BorderedContainer, {
|
|
size = self.props.size,
|
|
position = self.props.position,
|
|
anchorPoint = self.props.anchorPoint,
|
|
transparency = self.props.transparency,
|
|
}, {
|
|
Background = e("Frame", {
|
|
Size = UDim2.new(1, 0, 1, 0),
|
|
Position = UDim2.new(0, 0, 0, 0),
|
|
BorderSizePixel = 0,
|
|
BackgroundColor3 = self.scriptBackground,
|
|
ZIndex = -10,
|
|
}, {
|
|
UICorner = e("UICorner", {
|
|
CornerRadius = UDim.new(0, 5),
|
|
}),
|
|
}),
|
|
Main = e("Frame", {
|
|
Size = UDim2.new(1, -10, 1, -2),
|
|
Position = UDim2.new(0, 2, 0, 2),
|
|
BackgroundTransparency = 1,
|
|
[Roact.Change.AbsoluteSize] = function(rbx)
|
|
self.setWindowWidth(rbx.AbsoluteSize.X * 0.5 - 10)
|
|
end,
|
|
}, {
|
|
Separator = e("Frame", {
|
|
Size = UDim2.new(0, 2, 1, 0),
|
|
Position = UDim2.new(0.5, 0, 0, 0),
|
|
AnchorPoint = Vector2.new(0.5, 0),
|
|
BorderSizePixel = 0,
|
|
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
|
BackgroundTransparency = 0.5,
|
|
}),
|
|
Current = e(VirtualScroller, {
|
|
position = UDim2.new(0, 0, 0, 0),
|
|
size = UDim2.new(0.5, -1, 1, 0),
|
|
transparency = self.props.transparency,
|
|
count = maxLines,
|
|
updateEvent = self.updateEvent.Event,
|
|
canvasWidth = canvasWidth,
|
|
canvasPosition = self.canvasPosition,
|
|
onCanvasPositionChanged = self.setCanvasPosition,
|
|
render = function(i)
|
|
local lineDiffs = currentDiffs[i]
|
|
local diffFrames = table.create(if lineDiffs then #lineDiffs else 0)
|
|
|
|
-- Show diff markers over the specific changed characters
|
|
if lineDiffs then
|
|
local charWidth = math.round(theme.TextSize.Code * 0.5)
|
|
for diffIdx, diff in lineDiffs do
|
|
local start, stop = diff.start, diff.stop
|
|
diffFrames[diffIdx] = e("Frame", {
|
|
Size = if #lineDiffs == 1
|
|
and start == 0
|
|
and stop == 0
|
|
then UDim2.fromScale(1, 1)
|
|
else UDim2.new(
|
|
0,
|
|
math.max(charWidth * (stop - start), charWidth * 0.4),
|
|
1,
|
|
0
|
|
),
|
|
Position = UDim2.fromOffset(charWidth * start, 0),
|
|
BackgroundColor3 = theme.Diff.Background.Remove,
|
|
BackgroundTransparency = 0.85,
|
|
BorderSizePixel = 0,
|
|
ZIndex = -1,
|
|
})
|
|
end
|
|
end
|
|
|
|
return Roact.createFragment({
|
|
LineNumber = e("TextLabel", {
|
|
Size = UDim2.new(0, lineNumberWidth + 8, 1, 0),
|
|
Text = i,
|
|
BackgroundColor3 = Color3.new(0, 0, 0),
|
|
BackgroundTransparency = 0.9,
|
|
BorderSizePixel = 0,
|
|
FontFace = theme.Font.Code,
|
|
TextSize = theme.TextSize.Body,
|
|
TextColor3 = if lineDiffs then theme.Diff.Background.Remove else theme.SubTextColor,
|
|
TextXAlignment = Enum.TextXAlignment.Right,
|
|
}, {
|
|
Padding = e("UIPadding", { PaddingRight = UDim.new(0, 6) }),
|
|
}),
|
|
Content = e("Frame", {
|
|
Size = UDim2.new(1, -(lineNumberWidth + 10), 1, 0),
|
|
Position = UDim2.fromScale(1, 0),
|
|
AnchorPoint = Vector2.new(1, 0),
|
|
BackgroundColor3 = theme.Diff.Background.Remove,
|
|
BackgroundTransparency = if lineDiffs then 0.95 else 1,
|
|
BorderSizePixel = 0,
|
|
}, {
|
|
CodeLabel = e("TextLabel", {
|
|
Size = UDim2.fromScale(1, 1),
|
|
Position = UDim2.fromScale(0, 0),
|
|
Text = currentRichTextLines[i] or "",
|
|
RichText = true,
|
|
BackgroundTransparency = 1,
|
|
BorderSizePixel = 0,
|
|
FontFace = theme.Font.Code,
|
|
TextSize = theme.TextSize.Code,
|
|
TextXAlignment = Enum.TextXAlignment.Left,
|
|
TextYAlignment = Enum.TextYAlignment.Top,
|
|
TextColor3 = Color3.fromRGB(255, 255, 255),
|
|
}),
|
|
DiffFrames = Roact.createFragment(diffFrames),
|
|
}),
|
|
})
|
|
end,
|
|
getHeightBinding = function()
|
|
return self.lineHeight
|
|
end,
|
|
}),
|
|
Incoming = e(VirtualScroller, {
|
|
position = UDim2.new(0.5, 1, 0, 0),
|
|
size = UDim2.new(0.5, -1, 1, 0),
|
|
transparency = self.props.transparency,
|
|
count = maxLines,
|
|
updateEvent = self.updateEvent.Event,
|
|
canvasWidth = canvasWidth,
|
|
canvasPosition = self.canvasPosition,
|
|
onCanvasPositionChanged = self.setCanvasPosition,
|
|
render = function(i)
|
|
local lineDiffs = incomingDiffs[i]
|
|
local diffFrames = table.create(if lineDiffs then #lineDiffs else 0)
|
|
|
|
-- Show diff markers over the specific changed characters
|
|
if lineDiffs then
|
|
local charWidth = math.round(theme.TextSize.Code * 0.5)
|
|
for diffIdx, diff in lineDiffs do
|
|
local start, stop = diff.start, diff.stop
|
|
diffFrames[diffIdx] = e("Frame", {
|
|
Size = if #lineDiffs == 1
|
|
and start == 0
|
|
and stop == 0
|
|
then UDim2.fromScale(1, 1)
|
|
else UDim2.new(
|
|
0,
|
|
math.max(charWidth * (stop - start), charWidth * 0.4),
|
|
1,
|
|
0
|
|
),
|
|
Position = UDim2.fromOffset(charWidth * start, 0),
|
|
BackgroundColor3 = theme.Diff.Background.Add,
|
|
BackgroundTransparency = 0.85,
|
|
BorderSizePixel = 0,
|
|
ZIndex = -1,
|
|
})
|
|
end
|
|
end
|
|
|
|
return Roact.createFragment({
|
|
LineNumber = e("TextLabel", {
|
|
Size = UDim2.new(0, lineNumberWidth + 8, 1, 0),
|
|
Text = i,
|
|
BackgroundColor3 = Color3.new(0, 0, 0),
|
|
BackgroundTransparency = 0.9,
|
|
BorderSizePixel = 0,
|
|
FontFace = theme.Font.Code,
|
|
TextSize = theme.TextSize.Body,
|
|
TextColor3 = if lineDiffs then theme.Diff.Background.Add else theme.SubTextColor,
|
|
TextXAlignment = Enum.TextXAlignment.Right,
|
|
}, {
|
|
Padding = e("UIPadding", { PaddingRight = UDim.new(0, 6) }),
|
|
}),
|
|
Content = e("Frame", {
|
|
Size = UDim2.new(1, -(lineNumberWidth + 10), 1, 0),
|
|
Position = UDim2.fromScale(1, 0),
|
|
AnchorPoint = Vector2.new(1, 0),
|
|
BackgroundColor3 = theme.Diff.Background.Add,
|
|
BackgroundTransparency = if lineDiffs then 0.95 else 1,
|
|
BorderSizePixel = 0,
|
|
}, {
|
|
CodeLabel = e("TextLabel", {
|
|
Size = UDim2.fromScale(1, 1),
|
|
Position = UDim2.fromScale(0, 0),
|
|
Text = incomingRichTextLines[i] or "",
|
|
RichText = true,
|
|
BackgroundColor3 = theme.Diff.Background.Add,
|
|
BackgroundTransparency = 1,
|
|
BorderSizePixel = 0,
|
|
FontFace = theme.Font.Code,
|
|
TextSize = theme.TextSize.Code,
|
|
TextXAlignment = Enum.TextXAlignment.Left,
|
|
TextYAlignment = Enum.TextYAlignment.Top,
|
|
TextColor3 = Color3.fromRGB(255, 255, 255),
|
|
}),
|
|
DiffFrames = Roact.createFragment(diffFrames),
|
|
}),
|
|
})
|
|
end,
|
|
getHeightBinding = function()
|
|
return self.lineHeight
|
|
end,
|
|
}),
|
|
}),
|
|
ScrollMarkers = e("Frame", {
|
|
Size = self.windowWidth:map(function(windowWidth)
|
|
return UDim2.new(0, 8, 1, -4 - (if canvasWidth > windowWidth then 10 else 0))
|
|
end),
|
|
Position = UDim2.new(1, -2, 0, 2),
|
|
AnchorPoint = Vector2.new(1, 0),
|
|
BackgroundTransparency = 1,
|
|
}, {
|
|
insertions = Roact.createFragment(insertionScrollMarkers),
|
|
removals = Roact.createFragment(removalScrollMarkers),
|
|
}),
|
|
})
|
|
end)
|
|
end
|
|
|
|
return StringDiffVisualizer
|