From 9bedbd70fd84d68f1d05072dce11e1a07a1b8d68 Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 13 Jan 2026 21:46:57 -0600 Subject: [PATCH] Add whitespace change detection utility and integrate into PatchTree. Enhanced PatchTree to count line changes specifically for Source properties, distinguishing between total and whitespace-only changes. Updated theme colors for visual representation of whitespace changes in the UI. --- .../Components/PatchVisualizer/DomLabel.lua | 77 ++++++++++++++----- plugin/src/App/Theme.lua | 2 + plugin/src/PatchTree.lua | 23 +++++- plugin/src/WhitespaceUtil.lua | 50 ++++++++++++ 4 files changed, 128 insertions(+), 24 deletions(-) create mode 100644 plugin/src/WhitespaceUtil.lua diff --git a/plugin/src/App/Components/PatchVisualizer/DomLabel.lua b/plugin/src/App/Components/PatchVisualizer/DomLabel.lua index bc9b110e..13322ac9 100644 --- a/plugin/src/App/Components/PatchVisualizer/DomLabel.lua +++ b/plugin/src/App/Components/PatchVisualizer/DomLabel.lua @@ -17,6 +17,40 @@ local ChangeList = require(script.Parent.ChangeList) local Tooltip = require(Plugin.App.Components.Tooltip) local ClassIcon = require(Plugin.App.Components.ClassIcon) +local function ChangeTag(props) + return Theme.with(function(theme) + local tagColor = props.color or theme.SubTextColor + return e("Frame", { + Size = UDim2.new(0, 0, 0, 16), + AutomaticSize = Enum.AutomaticSize.X, + BackgroundColor3 = tagColor, + BackgroundTransparency = props.transparency:map(function(t) + return 0.85 + (0.15 * t) + end), + LayoutOrder = props.layoutOrder or 1, + }, { + Corner = e("UICorner", { + CornerRadius = UDim.new(0, 3), + }), + Padding = e("UIPadding", { + PaddingLeft = UDim.new(0, 5), + PaddingRight = UDim.new(0, 5), + }), + Label = e("TextLabel", { + Text = props.text, + BackgroundTransparency = 1, + FontFace = theme.Font.Thin, + TextSize = theme.TextSize.Small, + TextColor3 = tagColor, + TextXAlignment = Enum.TextXAlignment.Center, + TextTransparency = props.transparency, + Size = UDim2.new(0, 0, 1, 0), + AutomaticSize = Enum.AutomaticSize.X, + }), + }) + end) +end + local Expansion = Roact.Component:extend("Expansion") function Expansion:render() @@ -247,30 +281,31 @@ function DomLabel:render() SortOrder = Enum.SortOrder.LayoutOrder, Padding = UDim.new(0, 4), }), - Edits = if props.changeInfo and props.changeInfo.edits - then e("TextLabel", { - Text = props.changeInfo.edits .. if props.changeInfo.failed then "," else "", - BackgroundTransparency = 1, - FontFace = theme.Font.Thin, - TextSize = theme.TextSize.Body, - TextColor3 = theme.SubTextColor, - TextTransparency = props.transparency, - Size = UDim2.new(0, 0, 0, theme.TextSize.Body), - AutomaticSize = Enum.AutomaticSize.X, - LayoutOrder = 2, + LinesTag = if props.changeInfo and props.changeInfo.lineChanges + then e(ChangeTag, { + text = (if props.changeInfo.isWhitespaceOnly then "Whitespace " else "Lines ") + .. (if props.changeInfo.lineChanges >= 10000 then "100+" else props.changeInfo.lineChanges), + color = if props.changeInfo.isWhitespaceOnly + then theme.Diff.WhitespaceOnly + else theme.Diff.Changes, + transparency = props.transparency, + layoutOrder = 1, + }) + else nil, + PropsTag = if props.changeInfo and props.changeInfo.propChanges + then e(ChangeTag, { + text = "Props " .. props.changeInfo.propChanges, + color = theme.Diff.Changes or theme.Diff.Background.Edit, + transparency = props.transparency, + layoutOrder = 2, }) else nil, Failed = if props.changeInfo and props.changeInfo.failed - then e("TextLabel", { - Text = props.changeInfo.failed, - BackgroundTransparency = 1, - FontFace = theme.Font.Thin, - TextSize = theme.TextSize.Body, - TextColor3 = theme.Diff.Warning, - TextTransparency = props.transparency, - Size = UDim2.new(0, 0, 0, theme.TextSize.Body), - AutomaticSize = Enum.AutomaticSize.X, - LayoutOrder = 6, + then e(ChangeTag, { + text = "Failed " .. props.changeInfo.failed, + color = theme.Diff.Warning, + transparency = props.transparency, + layoutOrder = 3, }) else nil, }), diff --git a/plugin/src/App/Theme.lua b/plugin/src/App/Theme.lua index 09e3bd57..53b98a46 100644 --- a/plugin/src/App/Theme.lua +++ b/plugin/src/App/Theme.lua @@ -173,6 +173,8 @@ function StudioProvider:updateTheme() Row = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText), Warning = studioTheme:GetColor(Enum.StudioStyleGuideColor.WarningText), + WhitespaceOnly = if isDark then Color3.fromRGB(140, 140, 140) else Color3.fromRGB(110, 110, 110), + Changes = if isDark then Color3.fromRGB(100, 140, 220) else Color3.fromRGB(40, 100, 180), Background = { -- Studio doesn't have good colors since their diffs use backgrounds, not text diff --git a/plugin/src/PatchTree.lua b/plugin/src/PatchTree.lua index 37deae11..fe6cd522 100644 --- a/plugin/src/PatchTree.lua +++ b/plugin/src/PatchTree.lua @@ -13,6 +13,7 @@ local Log = require(Packages.Log) local Timer = require(Plugin.Timer) local Types = require(Plugin.Types) +local WhitespaceUtil = require(Plugin.WhitespaceUtil) local decodeValue = require(Plugin.Reconciler.decodeValue) local getProperty = require(Plugin.Reconciler.getProperty) @@ -237,13 +238,16 @@ function PatchTree.build(patch, instanceMap, changeListHeaders) -- Gather detail text local changeList, changeInfo = nil, nil + local totalLineChanges = 0 + local whitespaceLineChanges = 0 + local hasSourceChange = false if next(change.changedProperties) or change.changedName then changeList = {} local changeIndex = 0 - local function addProp(prop: string, current: any?, incoming: any?, metadata: any?) + local function addProp(prop: string, current: any?, incoming: any?) changeIndex += 1 - changeList[changeIndex] = { prop, current, incoming, metadata } + changeList[changeIndex] = { prop, current, incoming } end -- Gather the changes @@ -256,6 +260,12 @@ function PatchTree.build(patch, instanceMap, changeListHeaders) local incomingSuccess, incomingValue = decodeValue(incoming, instanceMap) local currentSuccess, currentValue = getProperty(instance, prop) + -- Check if this is a Source property - count line differences + if prop == "Source" and currentSuccess and incomingSuccess then + hasSourceChange = true + totalLineChanges, whitespaceLineChanges = WhitespaceUtil.CountLineDifferences(currentValue, incomingValue) + end + addProp( prop, if currentSuccess then currentValue else "[Error]", @@ -263,8 +273,15 @@ function PatchTree.build(patch, instanceMap, changeListHeaders) ) end + -- Count non-Source property changes + local propCount = changeIndex - (if hasSourceChange then 1 else 0) + changeInfo = { - edits = changeIndex, + -- Line changes for Source (nil if no Source change) + lineChanges = if hasSourceChange and totalLineChanges > 0 then totalLineChanges else nil, + isWhitespaceOnly = hasSourceChange and totalLineChanges > 0 and totalLineChanges == whitespaceLineChanges, + -- Property changes (non-Source: Name, Attributes, etc.) + propChanges = if propCount > 0 then propCount else nil, } -- Sort changes and add header diff --git a/plugin/src/WhitespaceUtil.lua b/plugin/src/WhitespaceUtil.lua new file mode 100644 index 00000000..8d05f054 --- /dev/null +++ b/plugin/src/WhitespaceUtil.lua @@ -0,0 +1,50 @@ +--[[ + Utility for detecting whitespace-only changes in source code. +]] + +local WhitespaceUtil = {} + +local function SplitLines(str: string): { string } + local lines = {} + for line in string.gmatch(str .. "\n", "([^\n]*)\n") do + table.insert(lines, line) + end + return lines +end + +local function NormalizeLine(line: string): string + return string.gsub(string.gsub(line, "\r+$", ""), "[ \t]+$", "") +end + +-- Counts lines that differ between two strings (capped at 100) +-- Returns: totalDiff, whitespaceDiff (lines that only differ in whitespace) +-- Returns 10000, 10000 if exceeds 100 (frontend displays as "100+") +function WhitespaceUtil.CountLineDifferences(current: string, incoming: string): (number, number) + if type(current) ~= "string" or type(incoming) ~= "string" then + return 0, 0 + end + + local currentLines = SplitLines(current) + local incomingLines = SplitLines(incoming) + local maxLines = math.max(#currentLines, #incomingLines) + + local totalDiff, whitespaceDiff = 0, 0 + for i = 1, maxLines do + local currentLine = currentLines[i] or "" + local incomingLine = incomingLines[i] or "" + + if currentLine ~= incomingLine then + totalDiff += 1 + if NormalizeLine(currentLine) == NormalizeLine(incomingLine) then + whitespaceDiff += 1 + end + if totalDiff > 100 then + return 10000, 10000 + end + end + end + + return totalDiff, whitespaceDiff +end + +return WhitespaceUtil