/* -*- 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 "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 FilteredContentIterator; class OffsetEntry; class TextEditor; 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, }; RefPtr mDocument; nsCOMPtr mSelCon; RefPtr mTextEditor; RefPtr mFilteredIter; nsCOMPtr mPrevTextBlock; nsCOMPtr mNextTextBlock; nsTArray mOffsetTable; RefPtr mExtent; uint32_t mTxtSvcFilterType; int32_t mSelStartIndex; int32_t mSelStartOffset; int32_t mSelEndIndex; int32_t mSelEndOffset; IteratorStatus mIteratorStatus; protected: virtual ~TextServicesDocument(); 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, int32_t* aSelOffset, int32_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(int32_t aOffset, int32_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 DidDeleteNode(nsINode* aChild); void DidJoinNodes(nsINode& aLeftNode, nsINode& aRightNode); // 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, int32_t* aStartOffset, nsINode** aEndContainer, int32_t* aEndOffset); private: nsresult CreateFilteredContentIterator( const dom::AbstractRange* aAbstractRange, FilteredContentIterator** aFilteredIter); dom::Element* GetDocumentContentRootNode() const; already_AddRefed CreateDocumentContentRange(); already_AddRefed 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 IsBlockNode(nsIContent* aContent); static bool IsTextNode(nsIContent* aContent); static bool DidSkip(FilteredContentIterator* aFilteredIter); static void ClearDidSkip(FilteredContentIterator* aFilteredIter); static bool HasSameBlockNodeParent(nsIContent* aContent1, nsIContent* aContent2); MOZ_CAN_RUN_SCRIPT nsresult SetSelectionInternal(int32_t aOffset, int32_t aLength, bool aDoUpdate); MOZ_CAN_RUN_SCRIPT nsresult GetSelection(BlockSelectionStatus* aSelStatus, int32_t* aSelOffset, int32_t* aSelLength); MOZ_CAN_RUN_SCRIPT nsresult GetCollapsedSelection(BlockSelectionStatus* aSelStatus, int32_t* aSelOffset, int32_t* aSelLength); nsresult GetUncollapsedSelection(BlockSelectionStatus* aSelStatus, int32_t* aSelOffset, int32_t* aSelLength); bool SelectionIsCollapsed(); bool SelectionIsValid(); static nsresult CreateOffsetTable(nsTArray* aOffsetTable, FilteredContentIterator* aFilteredIter, IteratorStatus* aIteratorStatus, nsRange* aIterRange, nsAString* aStr); static nsresult ClearOffsetTable(nsTArray* aOffsetTable); static nsresult NodeHasOffsetEntry(nsTArray* aOffsetTable, nsINode* aNode, bool* aHasEntry, int32_t* aEntryIndex); nsresult RemoveInvalidOffsetEntries(); nsresult SplitOffsetEntry(int32_t aTableIndex, int32_t aOffsetIntoEntry); static nsresult FindWordBounds(nsTArray* aOffsetTable, nsString* aBlockStr, nsINode* aNode, int32_t aNodeOffset, nsINode** aWordStartNode, int32_t* aWordStartOffset, nsINode** aWordEndNode, int32_t* aWordEndOffset); }; } // namespace mozilla #endif // #ifndef mozilla_TextServicesDocument_h