mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
e2b2e03fb4
It's currently referred from outside only by `TextServicesDocument.cpp`. It's not under `libeditor` but in the editor module. Therefore, let's allow it to refer the header files under `libeditor` directly. Then, we can stop exposing `HTMLEditUtils.h`. Depends on D158633 Differential Revision: https://phabricator.services.mozilla.com/D158634
439 lines
15 KiB
C++
439 lines
15 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef mozilla_TextServicesDocument_h
|
|
#define mozilla_TextServicesDocument_h
|
|
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
#include "nsIEditActionListener.h"
|
|
#include "nsISupportsImpl.h"
|
|
#include "nsStringFwd.h"
|
|
#include "nsTArray.h"
|
|
#include "nscore.h"
|
|
|
|
class nsIContent;
|
|
class nsIEditor;
|
|
class nsINode;
|
|
class nsISelectionController;
|
|
class nsRange;
|
|
|
|
namespace mozilla {
|
|
|
|
class EditorBase;
|
|
class FilteredContentIterator;
|
|
class OffsetEntry;
|
|
enum class JoinNodesDirection; // Declared in HTMLEditHelpers.h
|
|
|
|
namespace dom {
|
|
class AbstractRange;
|
|
class Document;
|
|
class Element;
|
|
class StaticRange;
|
|
}; // namespace dom
|
|
|
|
/**
|
|
* The TextServicesDocument presents the document in as a bunch of flattened
|
|
* text blocks. Each text block can be retrieved as an nsString.
|
|
*/
|
|
class TextServicesDocument final : public nsIEditActionListener {
|
|
private:
|
|
enum class IteratorStatus : uint8_t {
|
|
// No iterator (I), or iterator doesn't point to anything valid.
|
|
eDone = 0,
|
|
// I points to first text node (TN) in current block (CB).
|
|
eValid,
|
|
// No TN in CB, I points to first TN in prev block.
|
|
ePrev,
|
|
// No TN in CB, I points to first TN in next block.
|
|
eNext,
|
|
};
|
|
|
|
class OffsetEntryArray final : public nsTArray<UniquePtr<OffsetEntry>> {
|
|
public:
|
|
/**
|
|
* Init() initializes this array with aFilteredIter.
|
|
*
|
|
* @param[in] aIterRange Can be nullptr.
|
|
* @param[out] aAllTextInBlock
|
|
* Returns all text in the block.
|
|
*/
|
|
Result<IteratorStatus, nsresult> Init(
|
|
FilteredContentIterator& aFilteredIter, IteratorStatus aIteratorStatus,
|
|
nsRange* aIterRange, nsAString* aAllTextInBlock = nullptr);
|
|
|
|
/**
|
|
* Returns index of first `OffsetEntry` which manages aTextNode.
|
|
*/
|
|
Maybe<size_t> FirstIndexOf(const dom::Text& aTextNode) const;
|
|
|
|
/**
|
|
* FindWordRange() returns a word range starting from aStartPointToScan
|
|
* in aAllTextInBlock.
|
|
*/
|
|
Result<EditorDOMRangeInTexts, nsresult> FindWordRange(
|
|
nsAString& aAllTextInBlock, const EditorRawDOMPoint& aStartPointToScan);
|
|
|
|
/**
|
|
* SplitElementAt() splits an `OffsetEntry` at aIndex if aOffsetInTextNode
|
|
* is middle of the range in the text node.
|
|
*
|
|
* @param aIndex Index of the entry which you want to split.
|
|
* @param aOffsetInTextNode
|
|
* Offset in the text node. I.e., the offset should be
|
|
* greater than 0 and less than `mLength`.
|
|
*/
|
|
nsresult SplitElementAt(size_t aIndex, uint32_t aOffsetInTextNode);
|
|
|
|
/**
|
|
* Remove all `OffsetEntry` elements whose `mIsValid` is set to false.
|
|
*/
|
|
void RemoveInvalidElements();
|
|
|
|
/**
|
|
* Called when non-collapsed selection will be deleted.
|
|
*/
|
|
nsresult WillDeleteSelection();
|
|
|
|
/**
|
|
* Called when non-collapsed selection is deleteded.
|
|
*/
|
|
OffsetEntry* DidDeleteSelection();
|
|
|
|
/**
|
|
* Called when aInsertedText is inserted.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT nsresult DidInsertText(dom::Selection* aSelection,
|
|
const nsAString& aInsertedString);
|
|
|
|
/**
|
|
* Called when selection range will be applied to the DOM Selection.
|
|
*/
|
|
Result<EditorRawDOMRangeInTexts, nsresult> WillSetSelection(
|
|
uint32_t aOffsetInTextInBlock, uint32_t aLength);
|
|
|
|
class Selection final {
|
|
public:
|
|
size_t StartIndex() const {
|
|
MOZ_ASSERT(IsIndexesSet());
|
|
return *mStartIndex;
|
|
}
|
|
size_t EndIndex() const {
|
|
MOZ_ASSERT(IsIndexesSet());
|
|
return *mEndIndex;
|
|
}
|
|
|
|
uint32_t StartOffsetInTextInBlock() const {
|
|
MOZ_ASSERT(IsSet());
|
|
return *mStartOffsetInTextInBlock;
|
|
}
|
|
uint32_t EndOffsetInTextInBlock() const {
|
|
MOZ_ASSERT(IsSet());
|
|
return *mEndOffsetInTextInBlock;
|
|
}
|
|
uint32_t LengthInTextInBlock() const {
|
|
MOZ_ASSERT(IsSet());
|
|
return *mEndOffsetInTextInBlock - *mStartOffsetInTextInBlock;
|
|
}
|
|
|
|
bool IsCollapsed() {
|
|
return !IsSet() || (IsInSameElement() && StartOffsetInTextInBlock() ==
|
|
EndOffsetInTextInBlock());
|
|
}
|
|
|
|
bool IsIndexesSet() const {
|
|
return mStartIndex.isSome() && mEndIndex.isSome();
|
|
}
|
|
bool IsSet() const {
|
|
return IsIndexesSet() && mStartOffsetInTextInBlock.isSome() &&
|
|
mEndOffsetInTextInBlock.isSome();
|
|
}
|
|
bool IsInSameElement() const {
|
|
return IsIndexesSet() && StartIndex() == EndIndex();
|
|
}
|
|
|
|
void Reset() {
|
|
mStartIndex.reset();
|
|
mEndIndex.reset();
|
|
mStartOffsetInTextInBlock.reset();
|
|
mEndOffsetInTextInBlock.reset();
|
|
}
|
|
void SetIndex(size_t aIndex) { mEndIndex = mStartIndex = Some(aIndex); }
|
|
void Set(size_t aIndex, uint32_t aOffsetInTextInBlock) {
|
|
mEndIndex = mStartIndex = Some(aIndex);
|
|
mStartOffsetInTextInBlock = mEndOffsetInTextInBlock =
|
|
Some(aOffsetInTextInBlock);
|
|
}
|
|
void SetIndexes(size_t aStartIndex, size_t aEndIndex) {
|
|
MOZ_DIAGNOSTIC_ASSERT(aStartIndex <= aEndIndex);
|
|
mStartIndex = Some(aStartIndex);
|
|
mEndIndex = Some(aEndIndex);
|
|
}
|
|
void Set(size_t aStartIndex, size_t aEndIndex,
|
|
uint32_t aStartOffsetInTextInBlock,
|
|
uint32_t aEndOffsetInTextInBlock) {
|
|
MOZ_DIAGNOSTIC_ASSERT(aStartIndex <= aEndIndex);
|
|
mStartIndex = Some(aStartIndex);
|
|
mEndIndex = Some(aEndIndex);
|
|
mStartOffsetInTextInBlock = Some(aStartOffsetInTextInBlock);
|
|
mEndOffsetInTextInBlock = Some(aEndOffsetInTextInBlock);
|
|
}
|
|
|
|
void CollapseToStart() {
|
|
MOZ_ASSERT(mStartIndex.isSome());
|
|
MOZ_ASSERT(mStartOffsetInTextInBlock.isSome());
|
|
mEndIndex = mStartIndex;
|
|
mEndOffsetInTextInBlock = mStartOffsetInTextInBlock;
|
|
}
|
|
|
|
private:
|
|
Maybe<size_t> mStartIndex;
|
|
Maybe<size_t> mEndIndex;
|
|
// Selected start and end offset in all text in a block element.
|
|
Maybe<uint32_t> mStartOffsetInTextInBlock;
|
|
Maybe<uint32_t> mEndOffsetInTextInBlock;
|
|
};
|
|
Selection mSelection;
|
|
};
|
|
|
|
RefPtr<dom::Document> mDocument;
|
|
nsCOMPtr<nsISelectionController> mSelCon;
|
|
RefPtr<EditorBase> mEditorBase;
|
|
RefPtr<FilteredContentIterator> mFilteredIter;
|
|
nsCOMPtr<nsIContent> mPrevTextBlock;
|
|
nsCOMPtr<nsIContent> mNextTextBlock;
|
|
OffsetEntryArray mOffsetTable;
|
|
RefPtr<nsRange> mExtent;
|
|
|
|
uint32_t mTxtSvcFilterType;
|
|
IteratorStatus mIteratorStatus;
|
|
|
|
protected:
|
|
virtual ~TextServicesDocument() = default;
|
|
|
|
public:
|
|
TextServicesDocument();
|
|
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
|
NS_DECL_CYCLE_COLLECTION_CLASS(TextServicesDocument)
|
|
|
|
/**
|
|
* Initializes the text services document to use a particular editor. The
|
|
* text services document will use the DOM document and presentation shell
|
|
* used by the editor.
|
|
*
|
|
* @param aEditor The editor to use.
|
|
*/
|
|
nsresult InitWithEditor(nsIEditor* aEditor);
|
|
|
|
/**
|
|
* Sets the range/extent over which the text services document will iterate.
|
|
* Note that InitWithEditor() should have been called prior to calling this
|
|
* method. If this method is never called, the text services defaults to
|
|
* iterating over the entire document.
|
|
*
|
|
* @param aAbstractRange The range to use. aAbstractRange must point to a
|
|
* valid range object.
|
|
*/
|
|
nsresult SetExtent(const dom::AbstractRange* aAbstractRange);
|
|
|
|
/**
|
|
* Expands the end points of the range so that it spans complete words. This
|
|
* call does not change any internal state of the text services document.
|
|
*
|
|
* @param aStaticRange [in/out] The range to be expanded/adjusted.
|
|
*/
|
|
nsresult ExpandRangeToWordBoundaries(dom::StaticRange* aStaticRange);
|
|
|
|
/**
|
|
* Sets the filter type to be used while iterating over content.
|
|
* This will clear the current filter type if it's not either
|
|
* FILTERTYPE_NORMAL or FILTERTYPE_MAIL.
|
|
*
|
|
* @param aFilterType The filter type to be used while iterating over
|
|
* content.
|
|
*/
|
|
nsresult SetFilterType(uint32_t aFilterType);
|
|
|
|
/**
|
|
* Returns the text in the current text block.
|
|
*
|
|
* @param aStr [OUT] This will contain the text.
|
|
*/
|
|
nsresult GetCurrentTextBlock(nsAString& aStr);
|
|
|
|
/**
|
|
* Tells the document to point to the first text block in the document. This
|
|
* method does not adjust the current cursor position or selection.
|
|
*/
|
|
nsresult FirstBlock();
|
|
|
|
enum class BlockSelectionStatus {
|
|
// There is no text block (TB) in or before the selection (S).
|
|
eBlockNotFound = 0,
|
|
// No TB in S, but found one before/after S.
|
|
eBlockOutside,
|
|
// S extends beyond the start and end of TB.
|
|
eBlockInside,
|
|
// TB contains entire S.
|
|
eBlockContains,
|
|
// S begins or ends in TB but extends outside of TB.
|
|
eBlockPartial,
|
|
};
|
|
|
|
/**
|
|
* Tells the document to point to the last text block that contains the
|
|
* current selection or caret.
|
|
*
|
|
* @param aSelectionStatus [OUT] This will contain the text block
|
|
* selection status.
|
|
* @param aSelectionOffset [OUT] This will contain the offset into the
|
|
* string returned by GetCurrentTextBlock() where
|
|
* the selection begins.
|
|
* @param aLength [OUT] This will contain the number of
|
|
* characters that are selected in the string.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT
|
|
nsresult LastSelectedBlock(BlockSelectionStatus* aSelStatus,
|
|
uint32_t* aSelOffset, uint32_t* aSelLength);
|
|
|
|
/**
|
|
* Tells the document to point to the text block before the current one.
|
|
* This method will return NS_OK, even if there is no previous block.
|
|
* Callers should call IsDone() to check if we have gone beyond the first
|
|
* text block in the document.
|
|
*/
|
|
nsresult PrevBlock();
|
|
|
|
/**
|
|
* Tells the document to point to the text block after the current one.
|
|
* This method will return NS_OK, even if there is no next block. Callers
|
|
* should call IsDone() to check if we have gone beyond the last text block
|
|
* in the document.
|
|
*/
|
|
nsresult NextBlock();
|
|
|
|
/**
|
|
* IsDone() will always set aIsDone == false unless the document contains
|
|
* no text, PrevBlock() was called while the document was already pointing
|
|
* to the first text block in the document, or NextBlock() was called while
|
|
* the document was already pointing to the last text block in the document.
|
|
*
|
|
* @param aIsDone [OUT] This will contain the result.
|
|
*/
|
|
nsresult IsDone(bool* aIsDone);
|
|
|
|
/**
|
|
* SetSelection() allows the caller to set the selection based on an offset
|
|
* into the string returned by GetCurrentTextBlock(). A length of zero
|
|
* places the cursor at that offset. A positive non-zero length "n" selects
|
|
* n characters in the string.
|
|
*
|
|
* @param aOffset Offset into string returned by
|
|
* GetCurrentTextBlock().
|
|
* @param aLength Number of characters selected.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT nsresult SetSelection(uint32_t aOffset, uint32_t aLength);
|
|
|
|
/**
|
|
* Scrolls the document so that the current selection is visible.
|
|
*/
|
|
nsresult ScrollSelectionIntoView();
|
|
|
|
/**
|
|
* Deletes the text selected by SetSelection(). Calling DeleteSelection()
|
|
* with nothing selected, or with a collapsed selection (cursor) does
|
|
* nothing and returns NS_OK.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT
|
|
nsresult DeleteSelection();
|
|
|
|
/**
|
|
* Inserts the given text at the current cursor position. If there is a
|
|
* selection, it will be deleted before the text is inserted.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT
|
|
nsresult InsertText(const nsAString& aText);
|
|
|
|
/**
|
|
* nsIEditActionListener method implementations.
|
|
*/
|
|
NS_DECL_NSIEDITACTIONLISTENER
|
|
|
|
/**
|
|
* Actual edit action listeners. When you add new method here for listening
|
|
* to new edit action, you need to make it called by EditorBase.
|
|
* Additionally, you need to call it from proper method of
|
|
* nsIEditActionListener too because if this is created not for inline
|
|
* spell checker of the editor, edit actions will be notified via
|
|
* nsIEditActionListener (slow path, though).
|
|
*/
|
|
void DidDeleteContent(const nsIContent& aChildContent);
|
|
void DidJoinContents(const EditorRawDOMPoint& aJoinedPoint,
|
|
const nsIContent& aRemovedContent,
|
|
JoinNodesDirection aJoinNodesDirection);
|
|
|
|
private:
|
|
// TODO: We should get rid of this method since `aAbstractRange` has
|
|
// enough simple API to get them.
|
|
static nsresult GetRangeEndPoints(const dom::AbstractRange* aAbstractRange,
|
|
nsINode** aStartContainer,
|
|
uint32_t* aStartOffset,
|
|
nsINode** aEndContainer,
|
|
uint32_t* aEndOffset);
|
|
|
|
nsresult CreateFilteredContentIterator(
|
|
const dom::AbstractRange* aAbstractRange,
|
|
FilteredContentIterator** aFilteredIter);
|
|
|
|
dom::Element* GetDocumentContentRootNode() const;
|
|
already_AddRefed<nsRange> CreateDocumentContentRange();
|
|
already_AddRefed<nsRange> CreateDocumentContentRootToNodeOffsetRange(
|
|
nsINode* aParent, uint32_t aOffset, bool aToStart);
|
|
nsresult CreateDocumentContentIterator(
|
|
FilteredContentIterator** aFilteredIter);
|
|
|
|
nsresult AdjustContentIterator();
|
|
|
|
static nsresult FirstTextNode(FilteredContentIterator* aFilteredIter,
|
|
IteratorStatus* aIteratorStatus);
|
|
static nsresult LastTextNode(FilteredContentIterator* aFilteredIter,
|
|
IteratorStatus* aIteratorStatus);
|
|
|
|
static nsresult FirstTextNodeInCurrentBlock(
|
|
FilteredContentIterator* aFilteredIter);
|
|
static nsresult FirstTextNodeInPrevBlock(
|
|
FilteredContentIterator* aFilteredIter);
|
|
static nsresult FirstTextNodeInNextBlock(
|
|
FilteredContentIterator* aFilteredIter);
|
|
|
|
nsresult GetFirstTextNodeInPrevBlock(nsIContent** aContent);
|
|
nsresult GetFirstTextNodeInNextBlock(nsIContent** aContent);
|
|
|
|
static bool DidSkip(FilteredContentIterator* aFilteredIter);
|
|
static void ClearDidSkip(FilteredContentIterator* aFilteredIter);
|
|
|
|
static bool HasSameBlockNodeParent(dom::Text& aTextNode1,
|
|
dom::Text& aTextNode2);
|
|
|
|
MOZ_CAN_RUN_SCRIPT nsresult SetSelectionInternal(uint32_t aOffset,
|
|
uint32_t aLength,
|
|
bool aDoUpdate);
|
|
MOZ_CAN_RUN_SCRIPT nsresult GetSelection(BlockSelectionStatus* aSelStatus,
|
|
uint32_t* aSelOffset,
|
|
uint32_t* aSelLength);
|
|
MOZ_CAN_RUN_SCRIPT nsresult
|
|
GetCollapsedSelection(BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset,
|
|
uint32_t* aSelLength);
|
|
nsresult GetUncollapsedSelection(BlockSelectionStatus* aSelStatus,
|
|
uint32_t* aSelOffset, uint32_t* aSelLength);
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // #ifndef mozilla_TextServicesDocument_h
|