code: /* LanguageTool, a natural language style checker
   * Copyright (C) 2023 Jaume Ortolà
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
   * USA
   */
  package org.languagetool.tools;
  
  import java.util.List;
  import java.util.ArrayList;
  
  import com.github.difflib.DiffUtils;
  import com.github.difflib.patch.AbstractDelta;
  import com.github.difflib.patch.DeltaType;
  import com.github.difflib.text.DiffRowGenerator;
  
  public class DiffsAsMatches {
  
    public List<PseudoMatch> getPseudoMatches(String original, String revised) {
      List<PseudoMatch> matches = new ArrayList<>();
      List<String> origList = DiffRowGenerator.SPLITTER_BY_WORD.apply(original);
      List<String> revList = DiffRowGenerator.SPLITTER_BY_WORD.apply(revised);
      List<AbstractDelta<String>> inlineDeltas = DiffUtils.diff(origList, revList, DiffRowGenerator.DEFAULT_EQUALIZER)
          .getDeltas();
      PseudoMatch lastMatch = null;
      AbstractDelta<String> lastInlineDelta = null;
      for (AbstractDelta<String> inlineDelta : inlineDeltas) {
        String replacement = String.join("", inlineDelta.getTarget().getLines());
        int fromPos = 0;
        int errorIndex = inlineDelta.getSource().getPosition();
        int indexCorrection = 0; // in case of INSERT, underline the 2 previous tokens (including a whitespace)
        if (inlineDelta.getType() == DeltaType.INSERT) {
          indexCorrection = 2;
          if (errorIndex - indexCorrection < 0) {
            indexCorrection = 0;
          }
        }
        for (int i = 0; i < errorIndex - indexCorrection; i++) {
          fromPos += origList.get(i).length();
        }
        boolean wasLastWhitespace = false;
        if (errorIndex - 1 < origList.size() && errorIndex - 1 > -1) {
          wasLastWhitespace = origList.get(errorIndex - 1).equals(" ");
        }
  
        String underlinedError = String.join("", inlineDelta.getSource().getLines());
        int toPos = fromPos + underlinedError.length();
  
        String prefixReplacement = "";
        for (int i = errorIndex - indexCorrection; i < errorIndex; i++) {
          toPos += origList.get(i).length();
          prefixReplacement = prefixReplacement + origList.get(i);
        }
        replacement = prefixReplacement + replacement;
        // INSERT at the sentence start
        if (fromPos == 0 && toPos == 0) {
          toPos = origList.get(0).length();
          replacement = replacement + origList.get(0);
        }
        // remove unnecessary whitespace at the end in INSERT
        if (inlineDelta.getType() == DeltaType.INSERT && replacement.endsWith(" ") && replacement.length() > 2
            && wasLastWhitespace) {
          replacement = replacement.substring(0, replacement.length() - 1);
          toPos--;
        }
        PseudoMatch match;
        // serealiza -> se realiza CHANGE + INSERT -> 1 match
        if (lastMatch != null && lastInlineDelta.getType() == DeltaType.CHANGE
            && inlineDelta.getType() == DeltaType.INSERT
            && origList.get(inlineDelta.getSource().getPosition() - 1).equals(" ")
            && inlineDelta.getSource().getPosition() - 1 == lastInlineDelta.getSource().getPosition()
                + lastInlineDelta.getSource().getLines().size()) {
          String newReplacement = lastMatch.getReplacements().get(0) + replacement.substring(toPos - fromPos);
          match = new PseudoMatch(newReplacement, lastMatch.getFromPos(), toPos);
          matches.remove(matches.size() - 1);
          // CHANGE + DELETE
        } else if (lastMatch != null && inlineDelta.getType() == DeltaType.DELETE && wasLastWhitespace
            && lastMatch.getToPos() + 1 == fromPos) {
          String newReplacement = lastMatch.getReplacements().get(0);
          match = new PseudoMatch(newReplacement, lastMatch.getFromPos(), toPos - 1);
          matches.remove(matches.size() - 1);
        } else {
          match = new PseudoMatch(replacement, fromPos, toPos);
        }
        matches.add(match);
        lastMatch = match;
        lastInlineDelta = inlineDelta;
  
      }
      return matches;
    }
  
  }
  