You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求支持交互式操作的Visual Diff Patch Editor工具

寻找交互式可视化Diff补丁编辑器

我之前在Software Recommendations板块问过关于可视化Diff补丁编辑器的问题,但没拿到想要的代码示例。现在我需要一款交互式的同类工具,核心需求如下:

  • 支持直接编辑补丁内容,或者编辑左右对比面板里的代码内容
  • 能实时查看修改后的Diff结果,也可以执行补丁回退操作
  • 理想状态下,能像Github Pull Requests那样,支持同时编辑PR里的所有文件

我自己实现的两个相关工具

1. 基于diff2html的可视化Diff展示与提取工具

这个工具借助diff2html实现Diff的可视化展示,还能提取左右面板的内容到文本框中,代码如下:

<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.1/styles/github.min.css" />
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
</head>
<script>
var tas = document.getElementsByTagName("TEXTAREA");
function show(diffString) {
  var targetElement = document.getElementById('myDiffElement');
  var configuration = {
    drawFileList: false,
    fileListToggle: false,
    fileListStartVisible: false,
    fileContentToggle: false,
    matching: 'lines',
    outputFormat: 'side-by-side',
    synchronisedScroll: true,
    highlight: true,
    renderNothingWhenEmpty: false,
  };
  var diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);
  diff2htmlUi.draw();
  diff2htmlUi.highlightCode();
  var tabs = document.getElementsByTagName("TABLE");
  tas[1].value = printTab(tabs[0]);
  tas[2].value = printTab(tabs[1]);
}
function printTab(t) {
  var o = "";
  for (var r = 1; r < t.rows.length; r++) {
    var cs = [];
    for (var c = 1; c < t.rows[r].cells.length; c++) {
      cs.push(t.rows[r].cells[c].innerText);
    }
    o += cs.join('\t') + '\n';
  }
  return o;
}
function demo() {
  show(tas[0].value);
}
setTimeout(demo, 2000)
</script>
<body onload="">
<textarea onchange="show(this.value)" rows="4" cols="20">diff --git a/https://codepen.io/HatScripts/full/QvWVgP b/https://codepen.io/HatScripts/full/QvWVgP @@ -1,3 +1,3 @@ function example() --- a/https://codepen.io/HatScripts/full/QvWVgP +++ b/https://codepen.io/HatScripts/full/QvWVgP function example() { - return "pizza"; + return "pasta"; }</textarea>
<textarea rows="4" cols="25"></textarea>
<textarea rows="4" cols="25"></textarea>
<div id="myDiffElement"></div>
</body>

2. 补丁文件转换与过滤工具

这个工具可以统一缩减空白字符、强制转为小写后再进行对比,不过我不确定它能不能处理所有类型的补丁文件,代码如下:

<body onload="init()">
<textarea ondrop="droppper(event)" rows=60 cols=120 onchange="process(this.value)"></textarea>
<textarea rows=60 cols=120></textarea>
</body>
<script>
var ta;
function transformLeft(i) {
  return i.toLocaleLowerCase().replace(/\s+/g, ' ');
}
function transformRight(i) {
  return i.toLocaleLowerCase().replace(/\s+/g, ' ');
}
function process(t) {
  var res = [], head = { prev:[], now:[], tl:[], tr:[], tempRows:[] }, processed = [];
  var lastDiff = null;
  var r = t.split('\n');
  if (!r[r.length]) r.pop();
  var counts = null;
  for(var i of r) {
    var type = null;
    if (i.substr(0,5) == 'diff ') {
      head = { prev:[], now:[], tl:[], tr:[], tempRows:[] };
      counts = null;
      type = 'diff';
    } else if (i.substr(0,6) == 'index ') type = 'index';
    else if (i.substr(0,4) == '--- ') type = '---';
    else if (i.substr(0,4) == '+++ ') type = '+++';
    else if (i.substr(0,3) == '@@ ') {
      type = '@@';
      counts = i.substr(3, i.length - 6).split(' ');
      if (!head.lines || head['@@'] != i) {
        head.lines = {
          prev: parseInt(counts[0].replace(/\.+,/, '').replace(/[+-]/, '')),
          now: parseInt(counts[1].replace(/\.+,/, '').replace(/[+-]/, ''))
        };
      }
      counts = i;
      continue;
    }
    if (type && !head[type]) {
      head[type] = i;
      continue;
    }
    if (type == null && !head.lines) {
      var ignored = {
        "copy from ":2,
        "copy to ":2,
        "new mode ":2,
        "old mode ":2,
        "rename from ":2,
        "rename to ":2,
        "similarity index ":2,
        "dissimilarity index ":3,
        "deleted file mode ":3,
        "new file mode ":3
      };
      var space = i.indexOf(' ');
      if (space > 0) {
        space++;
        if (ignored[i.substr(0, space)] == 1) {
          continue;
        }
        space = i.indexOf(' ', space);
        space++;
        if (ignored[i.substr(0, space)] == 2) {
          continue;
        }
        space = i.indexOf(' ', space);
        space++;
        if (ignored[i.substr(0, space)] == 3) {
          continue;
        }
      }
      alert("Unexpected content " + i + " !");
    }
    head.tempRows.push(i);
    var diff = 0;
    if (i[0] == '-') {
      diff--;
      head.lines.prev--;
      head.prev.push(i);
      head.tl.push(transformLeft(i.substr(1)));
    } else if (i[0] == '+') {
      diff++;
      head.lines.now--;
      head.now.push(i);
      head.tr.push(transformRight(i.substr(1)));
    } else {
      head.lines.prev--;
      head.prev.push(i);
      head.lines.now--;
      head.now.push(i);
    }
    if (diff != 0 & i.substr(1, 11) == "Subproject ") {
      continue;
    }
    if (head.lines.prev == 0 && head.lines.now == 0) {
      if (head.tl.join('\n') != head.tr.join('\n')) {
        if (!lastDiff || lastDiff != head.diff) {
          lastDiff = head.diff;
          res.push(head.diff);
          res.push(head.index);
          res.push(head['---']);
          res.push(head['+++']);
        }
        res.push(counts);
        res.push(head.tempRows.join('\n'));
        processed.push({ diff:head.diff, index:head.index, "---":head['---'], "+++":head['+++'], cnts: head.counts, rows: head.tempRows });
      }
      head.tl = [];
      head.tr = [];
      head.prev = [];
      head.now = [];
      head.tempRows = [];
      counts = null;
    }
  }
  ta[0].value = JSON.stringify(processed,null,2);
  ta[1].value = res.join('\n');
}
function init() {
  ta = document.getElementsByTagName("TEXTAREA");
}
function droppper(e) {
  e.stopPropagation();
  e.preventDefault();
  var files = e.dataTransfer.files; // Array of all files
  for (var i=0, file; file=files[i]; i++) {
    var reader = new FileReader();
    reader.onload = function(e2) {
      ta[0].value = e2.target.result;
      process(ta[0].value);
    }
    reader.readAsText(file);
  }
}
</script>

内容的提问来源于stack exchange,提问作者Jan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 09:04:10