blob: e2c09f772a29a6cf1ca0035bab1ea696ec60520c [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/editing/markers/suggestion_marker_list_impl.h"
#include "third_party/blink/renderer/core/editing/markers/suggestion_marker_replacement_scope.h"
#include "third_party/blink/renderer/core/editing/markers/unsorted_document_marker_list_editor.h"
namespace blink {
namespace {
UChar32 GetCodePointAt(const String& text, wtf_size_t index) {
UChar32 c;
U16_GET(text, 0, index, text.length(), c);
return c;
}
base::Optional<DocumentMarker::MarkerOffsets>
ComputeOffsetsAfterNonSuggestionEditingOperating(const DocumentMarker& marker,
const String& node_text,
unsigned offset,
unsigned old_length,
unsigned new_length) {
unsigned marker_start = marker.StartOffset();
unsigned marker_end = marker.EndOffset();
// Marked text was modified
if (offset < marker_end && offset + old_length > marker_start)
return {};
// Text inserted/replaced immediately after the marker, remove marker if first
// character is a (Unicode) letter or digit
if (offset == marker_end && new_length > 0) {
if (WTF::unicode::IsAlphanumeric(GetCodePointAt(node_text, offset)))
return {};
return marker.ComputeOffsetsAfterShift(offset, old_length, new_length);
}
// Text inserted/replaced immediately before the marker, remove marker if
// first character is a (Unicode) letter or digit
if (offset == marker_start && new_length > 0) {
if (WTF::unicode::IsAlphanumeric(
GetCodePointAt(node_text, offset + new_length - 1)))
return {};
return marker.ComputeOffsetsAfterShift(offset, old_length, new_length);
}
// Don't care if text was deleted immediately before or after the marker
return marker.ComputeOffsetsAfterShift(offset, old_length, new_length);
}
} // namespace
DocumentMarker::MarkerType SuggestionMarkerListImpl::MarkerType() const {
return DocumentMarker::kSuggestion;
}
bool SuggestionMarkerListImpl::IsEmpty() const {
return markers_.IsEmpty();
}
void SuggestionMarkerListImpl::Add(DocumentMarker* marker) {
DCHECK_EQ(DocumentMarker::kSuggestion, marker->GetType());
markers_.push_back(marker);
}
void SuggestionMarkerListImpl::Clear() {
markers_.clear();
}
const HeapVector<Member<DocumentMarker>>& SuggestionMarkerListImpl::GetMarkers()
const {
return markers_;
}
DocumentMarker* SuggestionMarkerListImpl::FirstMarkerIntersectingRange(
unsigned start_offset,
unsigned end_offset) const {
return UnsortedDocumentMarkerListEditor::FirstMarkerIntersectingRange(
markers_, start_offset, end_offset);
}
HeapVector<Member<DocumentMarker>>
SuggestionMarkerListImpl::MarkersIntersectingRange(unsigned start_offset,
unsigned end_offset) const {
return UnsortedDocumentMarkerListEditor::MarkersIntersectingRange(
markers_, start_offset, end_offset);
}
bool SuggestionMarkerListImpl::MoveMarkers(int length,
DocumentMarkerList* dst_list) {
return UnsortedDocumentMarkerListEditor::MoveMarkers(&markers_, length,
dst_list);
}
bool SuggestionMarkerListImpl::RemoveMarkers(unsigned start_offset,
int length) {
return UnsortedDocumentMarkerListEditor::RemoveMarkers(&markers_,
start_offset, length);
}
bool SuggestionMarkerListImpl::ShiftMarkers(const String& node_text,
unsigned offset,
unsigned old_length,
unsigned new_length) {
if (SuggestionMarkerReplacementScope::CurrentlyInScope())
return ShiftMarkersForSuggestionReplacement(offset, old_length, new_length);
return ShiftMarkersForNonSuggestionEditingOperation(node_text, offset,
old_length, new_length);
}
bool SuggestionMarkerListImpl::ShiftMarkersForSuggestionReplacement(
unsigned offset,
unsigned old_length,
unsigned new_length) {
// Since suggestion markers are stored unsorted, the quickest way to perform
// this operation is to build a new list with the markers not removed by the
// shift.
bool did_shift_marker = false;
unsigned end_offset = offset + old_length;
HeapVector<Member<DocumentMarker>> unremoved_markers;
for (const Member<DocumentMarker>& marker : markers_) {
// Markers that intersect the replacement range, but do not fully contain
// it, should be removed.
const bool marker_intersects_replacement_range =
marker->StartOffset() < end_offset && marker->EndOffset() > offset;
const bool marker_contains_replacement_range =
marker->StartOffset() <= offset && marker->EndOffset() >= end_offset;
if (marker_intersects_replacement_range &&
!marker_contains_replacement_range) {
did_shift_marker = true;
continue;
}
base::Optional<DocumentMarker::MarkerOffsets> result =
marker->ComputeOffsetsAfterShift(offset, old_length, new_length);
if (result == base::nullopt) {
did_shift_marker = true;
continue;
}
if (marker->StartOffset() != result.value().start_offset ||
marker->EndOffset() != result.value().end_offset) {
marker->SetStartOffset(result.value().start_offset);
marker->SetEndOffset(result.value().end_offset);
did_shift_marker = true;
}
unremoved_markers.push_back(marker);
}
markers_ = std::move(unremoved_markers);
return did_shift_marker;
}
bool SuggestionMarkerListImpl::ShiftMarkersForNonSuggestionEditingOperation(
const String& node_text,
unsigned offset,
unsigned old_length,
unsigned new_length) {
// Since suggestion markers are stored unsorted, the quickest way to perform
// this operation is to build a new list with the markers not removed by the
// shift.
bool did_shift_marker = false;
HeapVector<Member<DocumentMarker>> unremoved_markers;
for (const Member<DocumentMarker>& marker : markers_) {
base::Optional<DocumentMarker::MarkerOffsets> result =
ComputeOffsetsAfterNonSuggestionEditingOperating(
*marker, node_text, offset, old_length, new_length);
if (!result) {
did_shift_marker = true;
continue;
}
if (marker->StartOffset() != result.value().start_offset ||
marker->EndOffset() != result.value().end_offset) {
marker->SetStartOffset(result.value().start_offset);
marker->SetEndOffset(result.value().end_offset);
did_shift_marker = true;
}
unremoved_markers.push_back(marker);
}
markers_ = std::move(unremoved_markers);
return did_shift_marker;
}
void SuggestionMarkerListImpl::Trace(Visitor* visitor) const {
visitor->Trace(markers_);
DocumentMarkerList::Trace(visitor);
}
bool SuggestionMarkerListImpl::RemoveMarkerByTag(int32_t tag) {
for (auto* it = markers_.begin(); it != markers_.end(); it++) {
if (To<SuggestionMarker>(it->Get())->Tag() == tag) {
markers_.erase(it);
return true;
}
}
return false;
}
bool SuggestionMarkerListImpl::RemoveMarkerByType(
const SuggestionMarker::SuggestionType& type) {
for (auto* it = markers_.begin(); it != markers_.end(); it++) {
if (To<SuggestionMarker>(it->Get())->GetSuggestionType() == type) {
markers_.erase(it);
return true;
}
}
return false;
}
} // namespace blink