910e62b5创建于 1月15日历史提交
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/base/ime/surrounding_text_tracker.h"

#include <string_view>
#include <utility>

#include "base/check.h"
#include "base/logging.h"
#include "base/notreached.h"

namespace ui {
namespace {

// Replaces the substring of the given str with the offset with replacement.
// If the range is outside of the str, str will not be updated (i.e. final
// result may not contain replacement) but at least offset should be adjusted.
void ReplaceString16WithOffset(std::u16string& str,
                               size_t& offset,
                               size_t pos,
                               size_t n,
                               const std::u16string& replacement) {
  if (offset + str.length() < pos) {
    // replacement starts after the known str. Do nothing.
    return;
  }
  if (pos + n < offset) {
    // replacement is in the range of [0:offset). Just adjust the offset.
    offset += replacement.length() - n;
    return;
  }

  // Here, we have overlap (including just concatenating) of the original
  // str and the replacement.
  if (pos < offset) {
    // Replacement starts before the current offset.
    // Merge the pattern, and adjust the offset.
    str.replace(0, n - (offset - pos), replacement);
    offset = pos;
    return;
  }

  // Here, the overlap starts the same as or after the offset.
  // In this case, offset is not changed.
  size_t begin = pos - offset;  // Begin index within str.
  str.replace(begin, std::min(n, str.length() - begin), replacement);
}

// Erases [pos:pos+n) from the given str with the offset.
void EraseString16WithOffset(std::u16string& str,
                             size_t& offset,
                             size_t pos,
                             size_t n) {
  if (offset + str.length() <= pos) {
    // The erasing range is after the str's range. Do nothing.
    return;
  }

  if (pos + n <= offset) {
    // The erasing range is included in [0:offset]. Just adjust the offset.
    offset -= n;
    return;
  }

  // Here we have to actually erase some range of str.
  if (pos < offset) {
    // The erasing range starts before the offset.
    str.erase(0, n - (offset - pos));
    offset = pos;
    return;
  }

  size_t begin = pos - offset;
  str.erase(begin, std::min(n, str.length() - begin));
}

}  // namespace

gfx::Range SurroundingTextTracker::State::GetSurroundingTextRange() const {
  return {utf16_offset, utf16_offset + surrounding_text.length()};
}

std::optional<std::u16string_view>
SurroundingTextTracker::State::GetCompositionText() const {
  if (composition.is_empty()) {
    // Represents no composition. Return empty composition text as a valid
    // result.
    return std::u16string_view();
  }

  if (!composition.IsBoundedBy(GetSurroundingTextRange())) {
    // composition range is out of the range. Return error.
    return std::nullopt;
  }

  return std::u16string_view(surrounding_text)
      .substr(composition.GetMin() - utf16_offset, composition.length());
}

SurroundingTextTracker::Entry::Entry(State state,
                                     base::RepeatingClosure command)
    : state(std::move(state)), command(std::move(command)) {}

SurroundingTextTracker::Entry::Entry(const Entry&) = default;
SurroundingTextTracker::Entry::Entry(Entry&&) = default;
SurroundingTextTracker::Entry& SurroundingTextTracker::Entry::operator=(
    const Entry&) = default;
SurroundingTextTracker::Entry& SurroundingTextTracker::Entry::operator=(
    Entry&&) = default;
SurroundingTextTracker::Entry::~Entry() = default;

SurroundingTextTracker::SurroundingTextTracker() {
  ResetInternal(u"", 0u, gfx::Range(0));
}

SurroundingTextTracker::~SurroundingTextTracker() = default;

void SurroundingTextTracker::Reset() {
  ResetInternal(u"", 0u, gfx::Range(0));
}

void SurroundingTextTracker::CancelComposition() {
  predicted_state_.composition = gfx::Range();
  // TODO(b/267944900): Determine if the expectations need to be updated as
  // well.
  expected_updates_.clear();
}

SurroundingTextTracker::UpdateResult SurroundingTextTracker::Update(
    const std::u16string_view surrounding_text,
    size_t utf16_offset,
    const gfx::Range& selection) {
  for (auto it = expected_updates_.begin(); it != expected_updates_.end();
       ++it) {
    if (it->state.selection != selection) {
      continue;
    }

    // TODO(crbug.com/40251329): Limit the trailing text to support cases
    // where trailing text is truncated.
    size_t compare_begin = std::max(utf16_offset, it->state.utf16_offset);
    std::u16string_view target =
        surrounding_text.substr(compare_begin - utf16_offset);
    std::u16string_view history =
        std::u16string_view(it->state.surrounding_text)
            .substr(compare_begin - it->state.utf16_offset);

    if (target != history) {
      continue;
    }

    // Found the target state, but it may be different from the one we
    // estimate. Because the Update may be called multiple times for the same
    // event. Check if the recorded state is exact same here to skip unneeded
    // recalculation.
    if (it->state.surrounding_text == surrounding_text &&
        it->state.utf16_offset == utf16_offset &&
        it->state.selection == selection) {
      expected_updates_.erase(expected_updates_.begin(), it);
      return UpdateResult::kUpdated;
    }

    // Otherwise, recalculate the predicts.
    predicted_state_ = State{
        std::u16string(surrounding_text), utf16_offset, selection,
        predicted_state_.composition,  // Carried from the original state.
    };

    base::RepeatingClosure current_command = std::move(it->command);
    std::vector<base::RepeatingClosure> remaining_commands;
    for (++it; it != expected_updates_.end(); ++it) {
      remaining_commands.push_back(std::move(it->command));
    }
    expected_updates_.clear();
    expected_updates_.emplace_back(predicted_state_,
                                   std::move(current_command));

    // Replay all remaining commands to re-calculate predicted states from the
    // given one.
    for (auto& command : remaining_commands) {
      command.Run();
    }
    return UpdateResult::kUpdated;
  }

  VLOG(1) << "Unknown surrounding text update is found";
  ResetInternal(surrounding_text, utf16_offset, selection);
  return UpdateResult::kReset;
}

void SurroundingTextTracker::OnSetEditableSelectionRange(
    const gfx::Range& range) {
  predicted_state_.selection = range;
  expected_updates_.emplace_back(
      predicted_state_,
      base::BindRepeating(&SurroundingTextTracker::OnSetEditableSelectionRange,
                          base::Unretained(this), range));
}

void SurroundingTextTracker::OnSetCompositionText(
    const ui::CompositionText& composition) {
  // If it has a composition text already, replace it.
  // Otherwise, replace (or insert) selected text.
  const gfx::Range& old_range = predicted_state_.composition.is_empty()
                                    ? predicted_state_.selection
                                    : predicted_state_.composition;
  size_t composition_begin = old_range.GetMin();
  if (old_range.GetMax() < predicted_state_.utf16_offset ||
      old_range.GetMin() > predicted_state_.utf16_offset +
                               predicted_state_.surrounding_text.length()) {
    predicted_state_.surrounding_text = composition.text;
    predicted_state_.utf16_offset = composition_begin;
  } else {
    ReplaceString16WithOffset(predicted_state_.surrounding_text,
                              predicted_state_.utf16_offset, composition_begin,
                              old_range.length(), composition.text);
  }
  predicted_state_.selection =
      gfx::Range(composition_begin + composition.selection.start(),
                 composition_begin + composition.selection.end());
  predicted_state_.composition = gfx::Range(
      composition_begin, composition_begin + composition.text.length());
  expected_updates_.emplace_back(
      predicted_state_,
      base::BindRepeating(&SurroundingTextTracker::OnSetCompositionText,
                          base::Unretained(this), composition));
}

void SurroundingTextTracker::OnSetCompositionFromExistingText(
    const gfx::Range& range) {
  predicted_state_.composition = range;
  expected_updates_.emplace_back(
      predicted_state_,
      base::BindRepeating(
          &SurroundingTextTracker::OnSetCompositionFromExistingText,
          base::Unretained(this), range));
}

void SurroundingTextTracker::OnConfirmCompositionText(bool keep_selection) {
  if (!predicted_state_.composition.is_empty()) {
    if (!keep_selection && !predicted_state_.composition.is_empty()) {
      predicted_state_.selection =
          gfx::Range(predicted_state_.composition.end());
    }
    predicted_state_.composition = gfx::Range();
  }
  expected_updates_.emplace_back(
      predicted_state_,
      base::BindRepeating(&SurroundingTextTracker::OnConfirmCompositionText,
                          base::Unretained(this), keep_selection));
}

void SurroundingTextTracker::OnClearCompositionText() {
  if (!predicted_state_.composition.is_empty()) {
    EraseString16WithOffset(predicted_state_.surrounding_text,
                            predicted_state_.utf16_offset,
                            predicted_state_.composition.GetMin(),
                            predicted_state_.composition.length());
    // Set selection to the position where composition existed.
    predicted_state_.selection =
        gfx::Range(predicted_state_.composition.GetMin());
    predicted_state_.composition = gfx::Range();
  }
  expected_updates_.emplace_back(
      predicted_state_,
      base::BindRepeating(&SurroundingTextTracker::OnClearCompositionText,
                          base::Unretained(this)));
}

void SurroundingTextTracker::OnInsertText(
    const std::u16string_view text,
    TextInputClient::InsertTextCursorBehavior cursor_behavior) {
  gfx::Range rewritten_range = predicted_state_.selection;
  if (!predicted_state_.composition.is_empty()) {
    // Cancel the current composition.
    if (predicted_state_.composition.Intersects(rewritten_range)) {
      // Selection and composition has overlap, so take the union here.
      // Just after this section, the whole range will be replaced by |text|.
      rewritten_range =
          gfx::Range(std::min(predicted_state_.composition.GetMin(),
                              rewritten_range.GetMin()),
                     std::max(predicted_state_.composition.GetMax(),
                              rewritten_range.GetMax()));
    } else {
      // Otherwise, remove the composition. If the composition appears before
      // the rewritten range, the offset needs to be updated.
      EraseString16WithOffset(predicted_state_.surrounding_text,
                              predicted_state_.utf16_offset,
                              predicted_state_.composition.GetMin(),
                              predicted_state_.composition.length());
      if (rewritten_range.GetMin() > predicted_state_.composition.GetMin()) {
        rewritten_range = gfx::Range(
            rewritten_range.start() - predicted_state_.composition.length(),
            rewritten_range.end() - predicted_state_.composition.length());
      }
    }
  }

  if (rewritten_range.GetMin() >
          predicted_state_.utf16_offset +
              predicted_state_.surrounding_text.length() ||
      rewritten_range.GetMax() < predicted_state_.utf16_offset) {
    predicted_state_.surrounding_text = std::u16string(text);
    predicted_state_.utf16_offset = rewritten_range.GetMin();
  } else {
    ReplaceString16WithOffset(predicted_state_.surrounding_text,
                              predicted_state_.utf16_offset,
                              rewritten_range.GetMin(),
                              rewritten_range.length(), std::u16string(text));
  }
  predicted_state_.selection =
      cursor_behavior ==
              TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText
          ? gfx::Range(rewritten_range.GetMin() + text.length())
          : gfx::Range(rewritten_range.GetMin());
  predicted_state_.composition = gfx::Range();
  expected_updates_.emplace_back(
      predicted_state_,
      base::BindRepeating(&SurroundingTextTracker::OnInsertText,
                          // Bind `text` as a `std::u16string` to avoid
                          // a dangling string_view when callbacks are run.
                          base::Unretained(this), std::u16string(text),
                          cursor_behavior));
}

void SurroundingTextTracker::OnExtendSelectionAndDelete(size_t before,
                                                        size_t after) {
  if (before != 0 || after != 0 || !predicted_state_.selection.is_empty() ||
      !predicted_state_.composition.is_empty()) {
    gfx::Range delete_range(
        predicted_state_.selection.GetMin() -
            std::min(before, predicted_state_.selection.GetMin()),
        predicted_state_.selection.GetMax() + after);
    if (!predicted_state_.composition.is_empty()) {
      // Cancel the current composition.
      if (predicted_state_.composition.Intersects(delete_range)) {
        // Expand the delete_range to include the whole composition range,
        // if there's some overlap.
        delete_range =
            gfx::Range(std::min(predicted_state_.composition.GetMin(),
                                delete_range.GetMin()),
                       std::max(predicted_state_.composition.GetMax(),
                                delete_range.GetMax()));
      } else {
        // Otherwise, remove the composition here. If the composition appears
        // before the delete_range, the offset needs to be updated.
        EraseString16WithOffset(predicted_state_.surrounding_text,
                                predicted_state_.utf16_offset,
                                predicted_state_.composition.GetMin(),
                                predicted_state_.composition.length());
        if (delete_range.GetMin() > predicted_state_.composition.GetMin()) {
          delete_range = gfx::Range(
              delete_range.start() - predicted_state_.composition.length(),
              delete_range.end() - predicted_state_.composition.length());
        }
      }
    }

    EraseString16WithOffset(predicted_state_.surrounding_text,
                            predicted_state_.utf16_offset,
                            delete_range.GetMin(), delete_range.length());
    predicted_state_.selection = gfx::Range(delete_range.GetMin());
    predicted_state_.composition = gfx::Range();
  }

  expected_updates_.emplace_back(
      predicted_state_,
      base::BindRepeating(&SurroundingTextTracker::OnExtendSelectionAndDelete,
                          base::Unretained(this), before, after));
}

void SurroundingTextTracker::ResetInternal(std::u16string_view surrounding_text,
                                           size_t utf16_offset,
                                           const gfx::Range& selection) {
  predicted_state_ = State{std::u16string(surrounding_text), utf16_offset,
                           selection, gfx::Range()};
  expected_updates_.clear();
  expected_updates_.emplace_back(predicted_state_, base::RepeatingClosure());
}

}  // namespace ui