Private/Get-InlineDiff.ps1
|
#Requires -Version 5.1 function Get-InlineDiff { <# .SYNOPSIS 変更があった 1 行を、変更箇所と共通部分の断片に分割する。 .DESCRIPTION Get-DiffAlignment が Changed と判定した行について、変更前後の行を比較します。 削除行と追加行が別の行なら、行内を比較しない。 変更箇所のトークン数が MaxToken を超える場合は、詳細に比較せず、変更箇所全体を 1 つの変更として返す。 行の大部分が異なるので、詳細に示しても読めず、計算時間がかかるためです。 .PARAMETER Left 変更前の行。空文字も受け取ります。 .PARAMETER Right 変更後の行。空文字も受け取ります。 .PARAMETER MaxToken 変更箇所を詳細に比較するトークン数の上限。 .OUTPUTS [hashtable] @{ Left = @( TextDiff.Segment, ... ) Right = @( TextDiff.Segment, ... ) } それぞれの断片の Text を順に連結すると、入力の行に一致します。 外側の組はモジュール内部でのみ使うため、ハッシュテーブルのままにしています。 .EXAMPLE Get-InlineDiff -Left ' name varying(100),' -Right ' name varying(20),' # Left : ' name varying(' / '100'(変更) / '),' # Right : ' name varying(' / '20'(変更) / '),' #> [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory)] [AllowEmptyString()] [string]$Left, [Parameter(Mandatory)] [AllowEmptyString()] [string]$Right, [ValidateRange(1, [int]::MaxValue)] [int]$MaxToken = 400 ) if ($Left -ceq $Right) { return @{ Left = @(Join-DiffSegment -Segments @(@{ Text = $Left; Changed = $false })) Right = @(Join-DiffSegment -Segments @(@{ Text = $Right; Changed = $false })) } } # --- 1. トークンに分割し、共通の先頭・末尾を除去する --- # トークン単位で分割する理由は、文字単位では長い行の比較に 1 行あたり 100 ミリ秒以上かかるため。 # 先に共通の先頭・末尾を除去する理由は、MaxToken を変更箇所だけで判定するため。 # 文字単位で除去すると、varying(100) と varying(20) の強調が「[10]0」「[2]0」とトークンの途中で切れる $allLeft = @(Split-DiffToken -Text $Left) $allRight = @(Split-DiffToken -Text $Right) $limit = [Math]::Min($allLeft.Count, $allRight.Count) $head = 0 while ($head -lt $limit -and $allLeft[$head] -ceq $allRight[$head]) { $head++ } $tail = 0 while ($tail -lt ($limit - $head) -and $allLeft[($allLeft.Count - 1 - $tail)] -ceq $allRight[($allRight.Count - 1 - $tail)]) { $tail++ } $leftList = [System.Collections.Generic.List[string]]::new([string[]]$allLeft) $rightList = [System.Collections.Generic.List[string]]::new([string[]]$allRight) $prefix = -join $leftList.GetRange(0, $head) $suffix = -join $leftList.GetRange($leftList.Count - $tail, $tail) $leftTokens = @($leftList.GetRange($head, $leftList.Count - $head - $tail)) $rightTokens = @($rightList.GetRange($head, $rightList.Count - $head - $tail)) $leftSegments = [System.Collections.Generic.List[hashtable]]::new() $rightSegments = [System.Collections.Generic.List[hashtable]]::new() $leftSegments.Add(@{ Text = $prefix; Changed = $false }) $rightSegments.Add(@{ Text = $prefix; Changed = $false }) # --- 2. 残りの変更箇所を Myers 法で比較する --- if ($leftTokens.Count -gt $MaxToken -or $rightTokens.Count -gt $MaxToken) { $leftSegments.Add(@{ Text = (-join $leftTokens); Changed = $true }) $rightSegments.Add(@{ Text = (-join $rightTokens); Changed = $true }) } else { $ids = ConvertTo-LineId -Left $leftTokens -Right $rightTokens $ops = @(Get-MyersOperation -Left $ids.Left -Right $ids.Right) foreach ($op in $ops) { switch -CaseSensitive ($op.Kind) { 'Same' { $leftSegments.Add(@{ Text = $leftTokens[$op.LeftIndex]; Changed = $false }) $rightSegments.Add(@{ Text = $rightTokens[$op.RightIndex]; Changed = $false }) } 'Deleted' { $leftSegments.Add(@{ Text = $leftTokens[$op.LeftIndex]; Changed = $true }) } 'Added' { $rightSegments.Add(@{ Text = $rightTokens[$op.RightIndex]; Changed = $true }) } } } } $leftSegments.Add(@{ Text = $suffix; Changed = $false }) $rightSegments.Add(@{ Text = $suffix; Changed = $false }) # --- 3. 隣接する同じ状態の断片を結合する --- return @{ Left = @(Join-DiffSegment -Segments $leftSegments.ToArray()) Right = @(Join-DiffSegment -Segments $rightSegments.ToArray()) } } |