package main import ( "encoding/json" "os" "strings" ) // diffLines is a line diff of two texts in the style of diff -u without the // hunk headers: " " unchanged lines near a change, "-" removed, "+" added. // It returns "" when the texts are equal. func diffLines(a, b string) string { x, y := strings.Split(a, "\n"), strings.Split(b, "\n") // Longest common subsequence table. lcs := make([][]int, len(x)+1) for i := range lcs { lcs[i] = make([]int, len(y)+1) } for i := len(x) - 1; i >= 0; i-- { for j := len(y) - 1; j >= 0; j-- { if x[i] == y[j] { lcs[i][j] = lcs[i+1][j+1] + 1 } else { lcs[i][j] = max(lcs[i+1][j], lcs[i][j+1]) } } } type line struct { op byte text string } var all []line i, j := 0, 0 for i < len(x) || j < len(y) { switch { case i < len(x) && j < len(y) && x[i] == y[j]: all = append(all, line{' ', x[i]}) i++ j++ case i < len(x) && (j == len(y) || lcs[i+1][j] >= lcs[i][j+1]): all = append(all, line{'-', x[i]}) i++ default: all = append(all, line{'+', y[j]}) j++ } } // Keep two lines of context around changes. const ctx = 2 keep := make([]bool, len(all)) changed := false for k, l := range all { if l.op != ' ' { changed = true for d := -ctx; d <= ctx; d++ { if k+d >= 0 && k+d < len(all) { keep[k+d] = true } } } } if !changed { return "" } var out strings.Builder skipped := false for k, l := range all { if !keep[k] { skipped = true continue } if skipped { out.WriteString("…\n") skipped = false } out.WriteString(string(l.op) + " " + l.text + "\n") } return out.String() } // pretty re-indents JSON with sorted keys, so two spellings of the same // settings compare equal. func pretty(data []byte) string { var v any if json.Unmarshal(data, &v) != nil { return strings.TrimSpace(string(data)) } out, _ := json.MarshalIndent(v, "", " ") return string(out) } // pendingChanges is the diff from the confirmed settings to the saved ones. func (s System) pendingChanges() string { cur, err1 := os.ReadFile(s.settingsPath()) conf, err2 := os.ReadFile(s.confirmedPath()) if err1 != nil || err2 != nil { return "" } return diffLines(pretty(conf), pretty(cur)) }