blob: aefdcb6a7b9165ead1c067d9e6884cb2e18c85d1 [file] [log] [blame]
// Copyright 2017-2020 The Verible Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "common/strings/diff.h"
#include <iostream>
#include <vector>
#include "absl/strings/str_split.h"
#include "common/strings/split.h"
#include "common/util/iterator_range.h"
#include "external_libs/editscript.h"
namespace verible {
using diff::Edit;
using diff::Edits;
using diff::Operation;
static char EditOperationToLineMarker(Operation op) {
switch (op) {
case Operation::DELETE:
return '-';
case Operation::EQUALS:
return ' ';
case Operation::INSERT:
return '+';
default:
return '?';
}
}
LineDiffs::LineDiffs(absl::string_view before, absl::string_view after)
: before_text(before),
after_text(after),
before_lines(SplitLines(before_text)),
after_lines(SplitLines(after_text)),
edits(diff::GetTokenDiffs(before_lines.begin(), before_lines.end(),
after_lines.begin(), after_lines.end())) {}
template <typename Iter>
static std::ostream& PrintLineRange(std::ostream& stream, char op, Iter start,
Iter end) {
for (const auto& line : make_range(start, end)) {
stream << op << line << std::endl;
}
return stream;
}
std::ostream& LineDiffs::PrintEdit(std::ostream& stream,
const Edit& edit) const {
const char op = EditOperationToLineMarker(edit.operation);
if (edit.operation == Operation::INSERT) {
PrintLineRange(stream, op, after_lines.begin() + edit.start,
after_lines.begin() + edit.end);
} else {
PrintLineRange(stream, op, before_lines.begin() + edit.start,
before_lines.begin() + edit.end);
}
return stream;
}
std::ostream& operator<<(std::ostream& stream, const LineDiffs& diffs) {
for (const auto& edit : diffs.edits) {
diffs.PrintEdit(stream, edit);
}
return stream;
}
LineNumberSet DiffEditsToAddedLineNumbers(const Edits& edits) {
LineNumberSet added_lines;
for (const auto& edit : edits) {
if (edit.operation == Operation::INSERT) {
// Add 1 to convert from 0-indexed to 1-indexed.
added_lines.Add(
{static_cast<int>(edit.start) + 1, static_cast<int>(edit.end) + 1});
}
}
return added_lines;
}
std::vector<diff::Edits> DiffEditsToPatchHunks(const diff::Edits& edits,
int common_context) {
const int split_threshold = common_context * 2;
std::vector<diff::Edits> hunks(1); // start with 1 empty destination vector
for (const diff::Edit& edit : edits) {
auto& current_hunk = hunks.back();
if (edit.operation == Operation::EQUALS) {
const int edit_size = edit.end - edit.start;
if (current_hunk.empty()) {
// For "end-pieces" (in this case, the head), threshold should be
// common_context, not split_threshold.
if (edit_size > common_context) {
// Add the tail end of this edit.
current_hunk.push_back(
diff::Edit{edit.operation, edit.end - common_context, edit.end});
} else {
// Add the whole edit.
current_hunk.push_back(edit);
}
} else { // !current_hunk.empty()
// We don't know what follows this edit, so this may still be oversized.
// A final pass will trim excess sizing of EQUALS edits in tail
// position.
if (edit_size > split_threshold) {
// Close off the current hunk.
current_hunk.push_back(diff::Edit{edit.operation, edit.start,
edit.start + common_context});
// Start the next hunk.
hunks.push_back(diff::Edits{
diff::Edit{edit.operation, edit.end - common_context, edit.end}});
} else {
// Add the whole edit.
current_hunk.push_back(edit);
}
}
} else { // operation is INSERT or DELETE
current_hunk.push_back(edit);
}
}
// The last hunk may have been started before knowing it was the last one.
// Remove if it is a no-op.
const auto& last_hunk = hunks.back(); // hunks is always non-empty
if (last_hunk.size() == 1 &&
last_hunk.front().operation == Operation::EQUALS) {
// This last hunk's only element is an Operation::EQUALS (no-change),
// so remove it.
hunks.pop_back();
}
// Trim excess EQUALS tail edits in each hunk.
for (auto& hunk : hunks) {
auto& tail = hunk.back();
if (tail.operation == Operation::EQUALS) {
if (tail.end - tail.start > common_context) {
tail.end = tail.start + common_context;
}
}
}
return hunks;
}
} // namespace verible