Commit MetaInfo

Revision7668a9fffb7fb6ef3ec30f686fd5925c4efb62f7 (tree)
Time2017-10-22 23:05:54
Author <exeal@user...>

Log Message

Enhanced documentAboutToBeChanged method to take a DocumentChange.

Change Summary

Incremental Difference

diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/content-assist/default-content-assistant.hpp
--- a/ascension/ascension/content-assist/default-content-assistant.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/content-assist/default-content-assistant.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -62,7 +62,7 @@
6262 void uninstall() override;
6363 void viewerBoundsChanged() BOOST_NOEXCEPT override;
6464 // kernel.DocumentListener
65- void documentAboutToBeChanged(const kernel::Document& document) override;
65+ void documentAboutToBeChanged(const kernel::Document& document, const kernel::DocumentChange& change) override;
6666 void documentChanged(const kernel::Document& document, const kernel::DocumentChange& change) override;
6767 // viewer.Caret.MotionSignal
6868 void caretMoved(const viewer::Caret& caret, const kernel::Region& regionBeforeMotion);
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/graphics/font/line-layout-vector.hpp
--- a/ascension/ascension/graphics/font/line-layout-vector.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/graphics/font/line-layout-vector.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -118,7 +118,7 @@
118118 void presentationStylistChanged();
119119 void updateLongestLine(boost::optional<Index> line, Scalar measure) BOOST_NOEXCEPT;
120120 // kernel.DocumentListener
121- void documentAboutToBeChanged(const kernel::Document& document);
121+ void documentAboutToBeChanged(const kernel::Document& document, const kernel::DocumentChange& change);
122122 void documentChanged(const kernel::Document& document, const kernel::DocumentChange& change);
123123 // kernel.DocumentPartitioningListener
124124 void documentPartitioningChanged(const kernel::Region& changedRegion);
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/kernel/abstract-point.hpp
--- a/ascension/ascension/kernel/abstract-point.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/kernel/abstract-point.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -57,9 +57,14 @@
5757 /// Called when @c Document#resetContent of the document was called.
5858 virtual void contentReset() = 0;
5959 /**
60- * Called when the content of the document was changed.
61- * @param change The change
62- */
60+ * Called when the content of the document is about to be changed.
61+ * @param change The change
62+ */
63+ virtual void documentAboutToBeChanged(const DocumentChange& change) = 0;
64+ /**
65+ * Called when the content of the document was changed.
66+ * @param change The change
67+ */
6368 virtual void documentChanged(const DocumentChange& change) = 0;
6469 /// Called when the document is disposed.
6570 void documentDisposed() BOOST_NOEXCEPT;
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/kernel/bookmarker.hpp
--- a/ascension/ascension/kernel/bookmarker.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/kernel/bookmarker.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -88,7 +88,7 @@
8888 private:
8989 ascension::detail::GapVector<Index>::iterator find(Index line) const BOOST_NOEXCEPT;
9090 // DocumentListener
91- void documentAboutToBeChanged(const Document& document) override;
91+ void documentAboutToBeChanged(const Document& document, const DocumentChange& change) override;
9292 void documentChanged(const Document& document, const DocumentChange& change) override;
9393 private:
9494 explicit Bookmarker(Document& document) BOOST_NOEXCEPT;
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/kernel/document-observers.hpp
--- a/ascension/ascension/kernel/document-observers.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/kernel/document-observers.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -29,8 +29,10 @@
2929 /**
3030 * The document is about to be changed.
3131 * @param document The document
32+ * @param change The modification content. Both @c change.erasedRegion() and @c change.insertedRegion() may
33+ * return an empty
3234 */
33- virtual void documentAboutToBeChanged(const Document& document) = 0;
35+ virtual void documentAboutToBeChanged(const Document& document, const DocumentChange& change) = 0;
3436 /**
3537 * The text was deleted or inserted.
3638 * @param document The document
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/kernel/partition.hpp
--- a/ascension/ascension/kernel/partition.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/kernel/partition.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -57,8 +57,11 @@
5757 DocumentPartitioner() BOOST_NOEXCEPT;
5858 void notifyDocument(const Region& changedRegion);
5959 private:
60- /// The document is about to be changed.
61- virtual void documentAboutToBeChanged() BOOST_NOEXCEPT = 0;
60+ /**
61+ * The document is about to be changed.
62+ * @param change The modification content
63+ */
64+ virtual void documentAboutToBeChanged(const DocumentChange& change) BOOST_NOEXCEPT = 0;
6265 /**
6366 * The document was changed.
6467 * @param change The modification content
@@ -87,7 +90,7 @@
8790 public:
8891 NullPartitioner() BOOST_NOEXCEPT;
8992 private:
90- void documentAboutToBeChanged() BOOST_NOEXCEPT override;
93+ void documentAboutToBeChanged(const DocumentChange& change) BOOST_NOEXCEPT override;
9194 void documentChanged(const DocumentChange& change) BOOST_NOEXCEPT override;
9295 void doGetPartition(const Position& at, DocumentPartition& partition) const BOOST_NOEXCEPT override;
9396 void doInstall() BOOST_NOEXCEPT override;
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/kernel/point.hpp
--- a/ascension/ascension/kernel/point.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/kernel/point.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -39,9 +39,11 @@
3939 private:
4040 // AbstractPoint
4141 void contentReset() override;
42+ void documentAboutToBeChanged(const DocumentChange& change) override;
4243 void documentChanged(const DocumentChange& change) override;
4344 private:
4445 Position position_;
46+ boost::optional<Position> destination_;
4547 MotionSignal motionSignal_;
4648 };
4749
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/kernel/searcher.hpp
--- a/ascension/ascension/kernel/searcher.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/kernel/searcher.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -325,7 +325,7 @@
325325 void setPatternToSearcher(bool pushToHistory);
326326 bool update();
327327 // kernel.DocumentListener
328- void documentAboutToBeChanged(const kernel::Document& document);
328+ void documentAboutToBeChanged(const kernel::Document& document, const kernel::DocumentChange& change);
329329 void documentChanged(const kernel::Document& document, const kernel::DocumentChange& change);
330330 // kernel.BookmarkListener
331331 void bookmarkChanged(Index line);
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/presentation/presentation.hpp
--- a/ascension/ascension/presentation/presentation.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/presentation/presentation.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -160,7 +160,7 @@
160160 void clearHyperlinksCache() BOOST_NOEXCEPT;
161161 std::shared_ptr<const DeclaredTextLineStyle> declaredTextLineStyle(Index line) const;
162162 // kernel.DocumentListener
163- void documentAboutToBeChanged(const kernel::Document& document);
163+ void documentAboutToBeChanged(const kernel::Document& document, const kernel::DocumentChange& change);
164164 void documentChanged(const kernel::Document& document, const kernel::DocumentChange& change);
165165 private:
166166 kernel::Document& document_;
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/rules/lexical-partitioner.hpp
--- a/ascension/ascension/rules/lexical-partitioner.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/rules/lexical-partitioner.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -60,7 +60,7 @@
6060 const StringPiece& line, Index offsetInLine, const kernel::ContentType& contentType) const BOOST_NOEXCEPT;
6161 void verify() const;
6262 // DocumentPartitioner
63- void documentAboutToBeChanged() BOOST_NOEXCEPT override;
63+ void documentAboutToBeChanged(const kernel::DocumentChange& change) BOOST_NOEXCEPT override;
6464 void documentChanged(const kernel::DocumentChange& change) BOOST_NOEXCEPT override;
6565 void doGetPartition(const kernel::Position& at, kernel::DocumentPartition& partition) const BOOST_NOEXCEPT override;
6666 void doInstall() BOOST_NOEXCEPT override;
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/viewer/caret.hpp
--- a/ascension/ascension/viewer/caret.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/viewer/caret.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -181,6 +181,7 @@
181181 void adjustInputMethodCompositionWindow();
182182 bool canPastePlatformData() const;
183183 void checkMatchBrackets();
184+ void documentAboutToBeChanged(const kernel::DocumentChange& change) override;
184185 void documentChanged(const kernel::DocumentChange& change) override;
185186 void fireCaretMoved(const SelectedRegion& regionBeforeMotion);
186187 void internalExtendSelection(void (*algorithm)(void));
@@ -198,7 +199,7 @@
198199 // VisualPoint.MotionSignal
199200 void pointMoved(const VisualPoint& self, const TextHit& oldHit);
200201 // kernel.DocumentListener
201- void documentAboutToBeChanged(const kernel::Document& document) override;
202+ void documentAboutToBeChanged(const kernel::Document& document, const kernel::DocumentChange& change) override;
202203 void documentChanged(const kernel::Document& document, const kernel::DocumentChange& change) override;
203204 // detail.InputMethodEventHandler
204205 void commitString(widgetapi::event::InputMethodEvent& event) BOOST_NOEXCEPT override;
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/viewer/text-area.hpp
--- a/ascension/ascension/viewer/text-area.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/viewer/text-area.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -136,7 +136,7 @@
136136 void visualLinesModified(const boost::integer_range<Index>& lines,
137137 SignedIndex sublinesDifference, bool documentChanged, bool longestLineChanged) BOOST_NOEXCEPT override;
138138 // kernel.DocumentListener
139- void documentAboutToBeChanged(const kernel::Document& document) override;
139+ void documentAboutToBeChanged(const kernel::Document& document, const kernel::DocumentChange& change) override;
140140 void documentChanged(const kernel::Document& document, const kernel::DocumentChange& change) override;
141141 // graphics.font.LineRenderingOptions
142142 std::unique_ptr<const graphics::font::InlineObject> endOfLine(Index line) const BOOST_NOEXCEPT override;
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/ascension/viewer/visual-point.hpp
--- a/ascension/ascension/viewer/visual-point.hpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/ascension/viewer/visual-point.hpp Sun Oct 22 23:05:54 2017 +0900
@@ -105,6 +105,7 @@
105105 virtual void moved(const TextHit& from) BOOST_NOEXCEPT;
106106 // kernel.AbstractPoint
107107 virtual void contentReset() override;
108+ virtual void documentAboutToBeChanged(const kernel::DocumentChange& change) override;
108109 virtual void documentChanged(const kernel::DocumentChange& change) override;
109110 private:
110111 void buildVisualLineCaches();
@@ -124,6 +125,7 @@
124125 MotionSignal motionSignal_;
125126 boost::optional<graphics::Scalar> positionInVisualLine_; // see rememberPositionInVisualLine
126127 bool crossingLines_; // true only when the point is moving across the different lines
128+ boost::optional<TextHit> hitAfterChange_;
127129 boost::optional<graphics::font::VisualLine> lineNumberCaches_; // caches
128130 friend class TextArea;
129131 #ifdef ASCENSION_ABANDONED_AT_VERSION_08
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/content-assist/default-content-assistant.cpp
--- a/ascension/src/content-assist/default-content-assistant.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/content-assist/default-content-assistant.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -133,7 +133,7 @@
133133 }
134134
135135 /// @see kernel#DocumentListener#documentAboutToBeChanged
136- void DefaultContentAssistant::documentAboutToBeChanged(const kernel::Document&) {
136+ void DefaultContentAssistant::documentAboutToBeChanged(const kernel::Document&, const kernel::DocumentChange&) {
137137 // do nothing
138138 }
139139
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/graphics/font/line-layout-vector.cpp
--- a/ascension/src/graphics/font/line-layout-vector.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/graphics/font/line-layout-vector.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -186,7 +186,7 @@
186186 }
187187
188188 /// @see kernel#DocumentListener#documentAboutToBeChanged
189- void LineLayoutVector::documentAboutToBeChanged(const kernel::Document&) {
189+ void LineLayoutVector::documentAboutToBeChanged(const kernel::Document&, const kernel::DocumentChange& change) {
190190 documentChangePhase_ = ABOUT_TO_CHANGE;
191191 }
192192
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/kernel/bookmarker.cpp
--- a/ascension/src/kernel/bookmarker.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/kernel/bookmarker.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -55,7 +55,7 @@
5555 }
5656
5757 /// @see DocumentListener#documentAboutToBeChanged
58- void Bookmarker::documentAboutToBeChanged(const Document&) {
58+ void Bookmarker::documentAboutToBeChanged(const Document&, const DocumentChange&) {
5959 // do nothing
6060 }
6161
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/kernel/document.cpp
--- a/ascension/src/kernel/document.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/kernel/document.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -318,28 +318,35 @@
318318 void Document::doResetContent() {
319319 }
320320
321- void Document::fireDocumentAboutToBeChanged() BOOST_NOEXCEPT {
322- if(partitioner_.get() != nullptr)
323- partitioner_->documentAboutToBeChanged();
324- BOOST_FOREACH(DocumentListener* listener, prenotifiedListeners_)
325- listener->documentAboutToBeChanged(*this);
326- BOOST_FOREACH(DocumentListener* listener, listeners_)
327- listener->documentAboutToBeChanged(*this);
321+ namespace {
322+ template<typename PartitionerMethod, typename ListenerMethod, typename PointMethod>
323+ inline void fireDocumentListeners(
324+ const Document& document, const DocumentChange& change,
325+ std::unique_ptr<DocumentPartitioner>& partitioner, std::list<DocumentListener*>& prenotifiedListeners, std::list<DocumentListener*>& listeners, std::set<AbstractPoint*>& points,
326+ PartitionerMethod partitionerMethod, ListenerMethod listenerMethod, PointMethod pointMethod) {
327+ if(partitioner.get() != nullptr)
328+ (partitioner.get()->*partitionerMethod)(change);
329+ if(pointMethod != nullptr) {
330+ BOOST_FOREACH(AbstractPoint* p, points) {
331+ if(p->adaptsToDocument())
332+ (p->*pointMethod)(change);
333+ }
334+ }
335+ BOOST_FOREACH(DocumentListener* listener, prenotifiedListeners)
336+ (listener->*listenerMethod)(document, change);
337+ BOOST_FOREACH(DocumentListener* listener, listeners)
338+ (listener->*listenerMethod)(document, change);
339+ }
340+ }
341+
342+ void Document::fireDocumentAboutToBeChanged(const DocumentChange& c, bool updateAllPoints /* = true */) BOOST_NOEXCEPT {
343+ fireDocumentListeners(*this, c, partitioner_, prenotifiedListeners_, listeners_, points_,
344+ &DocumentPartitioner::documentAboutToBeChanged, &DocumentListener::documentAboutToBeChanged, updateAllPoints ? &AbstractPoint::documentAboutToBeChanged : nullptr);
328345 }
329346
330347 void Document::fireDocumentChanged(const DocumentChange& c, bool updateAllPoints /* = true */) BOOST_NOEXCEPT {
331- if(partitioner_.get() != nullptr)
332- partitioner_->documentChanged(c);
333- if(updateAllPoints) {
334- BOOST_FOREACH(AbstractPoint* p, points_) {
335- if(p->adaptsToDocument())
336- p->documentChanged(c);
337- }
338- }
339- BOOST_FOREACH(DocumentListener* listener, prenotifiedListeners_)
340- listener->documentChanged(*this, c);
341- BOOST_FOREACH(DocumentListener* listener, listeners_)
342- listener->documentChanged(*this, c);
348+ fireDocumentListeners(*this, c, partitioner_, prenotifiedListeners_, listeners_, points_,
349+ &DocumentPartitioner::documentChanged, &DocumentListener::documentChanged, updateAllPoints ? &AbstractPoint::documentChanged : nullptr);
343350 }
344351
345352 /**
@@ -525,8 +532,9 @@
525532 p->contentReset();
526533 }
527534 bookmarker_->clear();
528-
529- fireDocumentAboutToBeChanged();
535+
536+ const DocumentChange c(region(), Region::makeEmpty(*boost::const_begin(region())));
537+ fireDocumentAboutToBeChanged(c, false);
530538 if(length_ != 0) {
531539 assert(!lines_.empty());
532540 for(std::size_t i = 0, c = lines_.size(); i < c; ++i)
@@ -536,8 +544,7 @@
536544 length_ = 0;
537545 ++revisionNumber_;
538546 }
539- const DocumentChange ca(region(), Region::makeEmpty(*boost::const_begin(region())));
540- fireDocumentChanged(ca, false);
547+ fireDocumentChanged(c, false);
541548 }
542549
543550 setReadOnly(false);
@@ -709,7 +716,7 @@
709716 }
710717
711718 /// @see DocumentPartitioner#documentAboutToBeChanged
712- void NullPartitioner::documentAboutToBeChanged() BOOST_NOEXCEPT {
719+ void NullPartitioner::documentAboutToBeChanged(const DocumentChange&) BOOST_NOEXCEPT {
713720 }
714721
715722 /// @see DocumentPartitioner#documentChanged
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/kernel/point.cpp
--- a/ascension/src/kernel/point.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/kernel/point.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -113,14 +113,24 @@
113113 moveTo(Position::zero());
114114 }
115115
116+ /// @see AbstractPoint#documentAboutToBeChanged
117+ void Point::documentAboutToBeChanged(const DocumentChange& change) {
118+ assert(!isDocumentDisposed());
119+ assert(adaptsToDocument());
120+ assert(destination_ == boost::none);
121+ destination_ = locations::updatePosition(position(), change, gravity());
122+ }
123+
116124 /// @see AbstractPoint#documentChanged
117125 void Point::documentChanged(const DocumentChange& change) {
118126 assert(!isDocumentDisposed());
119127 assert(adaptsToDocument());
120-// normalize();
121- const Position newPosition(locations::updatePosition(position(), change, gravity()));
122- if(newPosition != position())
123- moveTo(newPosition); // TODO: this may throw...
128+ if(destination_ != boost::none) {
129+ const auto newPosition(boost::get(destination_));
130+ destination_ = boost::none;
131+ if(newPosition != position())
132+ moveTo(boost::get(destination_)); // TODO: this may throw...
133+ }
124134 }
125135
126136 /**
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/kernel/searcher.cpp
--- a/ascension/src/kernel/searcher.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/kernel/searcher.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -828,7 +828,7 @@
828828 }
829829
830830 /// @see kernel#DocumentListener#documentAboutToBeChanged
831- void IncrementalSearcher::documentAboutToBeChanged(const kernel::Document&) {
831+ void IncrementalSearcher::documentAboutToBeChanged(const kernel::Document&, const kernel::DocumentChange&) {
832832 abort();
833833 }
834834
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/kernel/undo.cpp
--- a/ascension/src/kernel/undo.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/kernel/undo.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -616,7 +616,7 @@
616616 */
617617 Position Document::replace(const Region& region, const StringPiece& text) {
618618 if(changing_)
619- throw IllegalStateException("called in IDocumentListeners' notification.");
619+ throw IllegalStateException("called in DocumentListeners' notification.");
620620 else if(isReadOnly())
621621 throw ReadOnlyDocumentException();
622622 else if(kernel::line(*boost::const_end(region)) >= numberOfLines()
@@ -632,7 +632,6 @@
632632 // preprocess. these can't throw
633633 ascension::detail::ValueSaver<bool> writeLock(changing_);
634634 changing_ = true;
635- fireDocumentAboutToBeChanged();
636635
637636 // change the content
638637 const Position& beginning = *boost::const_begin(region);
@@ -640,28 +639,29 @@
640639 StringPiece::const_iterator nextNewline((text.cbegin() != nullptr) ? boost::find_first_of(text, text::NEWLINE_CHARACTERS) : text.cend());
641640 std::basic_stringbuf<Char> erasedString;
642641 Index erasedStringLength = 0, insertedStringLength = 0;
643- Position endOfInsertedString;
642+ Region insertedRegion;
644643 try {
645644 // simple cases: both erased region and inserted string are single line
646645 if(kernel::line(beginning) == kernel::line(end) && (text.cbegin() == nullptr || text.empty())) { // erase in single line
646+ insertedRegion = Region::makeEmpty(beginning);
647+ fireDocumentAboutToBeChanged(DocumentChange(region, insertedRegion));
647648 Line& line = *lines_[beginning.line];
648649 erasedString.sputn(line.text().data() + offsetInLine(beginning), static_cast<std::streamsize>(offsetInLine(end) - offsetInLine(beginning)));
649650 line.text_.erase(offsetInLine(beginning), offsetInLine(end) - offsetInLine(beginning));
650651 erasedStringLength += offsetInLine(end) - offsetInLine(beginning);
651- endOfInsertedString = beginning;
652652 } else if(boost::empty(region) && nextNewline == text.cend()) { // insert single line
653+ insertedRegion = Region::makeSingleLine(kernel::line(beginning), boost::irange(offsetInLine(beginning), offsetInLine(beginning) + text.length()));
654+ fireDocumentAboutToBeChanged(DocumentChange(region, insertedRegion));
653655 lines_[kernel::line(beginning)]->text_.insert(offsetInLine(beginning), text.cbegin(), text.length());
654656 insertedStringLength += text.length();
655- endOfInsertedString.line = kernel::line(beginning);
656- endOfInsertedString.offsetInLine = offsetInLine(beginning) + text.length();
657657 } else if(kernel::line(beginning) == kernel::line(end) && nextNewline == text.cend()) { // replace in single line
658+ insertedRegion = Region::makeSingleLine(kernel::line(beginning), boost::irange(offsetInLine(beginning), offsetInLine(beginning) + text.length()));
659+ fireDocumentAboutToBeChanged(DocumentChange(region, insertedRegion));
658660 Line& line = *lines_[beginning.line];
659661 erasedString.sputn(line.text().data() + offsetInLine(beginning), static_cast<std::streamsize>(offsetInLine(end) - offsetInLine(beginning)));
660662 line.text_.replace(offsetInLine(beginning), offsetInLine(end) - offsetInLine(beginning), text.cbegin(), text.length());
661663 erasedStringLength += offsetInLine(end) - offsetInLine(beginning);
662664 insertedStringLength += text.length();
663- endOfInsertedString.line = kernel::line(beginning);
664- endOfInsertedString.offsetInLine = offsetInLine(beginning) + text.length();
665665 }
666666 // complex case: erased region and/or inserted string are/is multi-line
667667 else {
@@ -683,9 +683,10 @@
683683 break;
684684 }
685685 }
686+
686687 // 2. allocate strings (lines except first) to insert newly. only when inserted string was multiline
687688 std::vector<Line*> allocatedLines;
688- const StringPiece::const_iterator firstNewline(nextNewline);
689+ insertedRegion = Region::makeEmpty(beginning);
689690 if(text.cbegin() != nullptr && nextNewline != text.cend()) {
690691 try {
691692 StringPiece::const_iterator p(std::next(nextNewline, text::eatNewline(nextNewline, text.cend())->asString().length()));
@@ -704,8 +705,7 @@
704705 }
705706 // merge last line
706707 Line& lastAllocatedLine = *allocatedLines.back();
707- endOfInsertedString.line = kernel::line(beginning) + allocatedLines.size();
708- endOfInsertedString.offsetInLine = lastAllocatedLine.text().length();
708+ insertedRegion = Region(*boost::const_begin(insertedRegion), Position(kernel::line(beginning) + allocatedLines.size(), lastAllocatedLine.text().length()));
709709 const Line& lastLine = *lines_[kernel::line(end)];
710710 const auto n = lastLine.text().length() - offsetInLine(end);
711711 lastAllocatedLine.text_.append(lastLine.text(), offsetInLine(end), n);
@@ -716,16 +716,24 @@
716716 delete line;
717717 throw;
718718 }
719- } else
720- endOfInsertedString = beginning;
719+ }
720+ const StringPiece::const_iterator firstNewline(nextNewline);
721+ const Index insertedLength = firstNewline - text.cbegin();
722+ if(allocatedLines.empty()) {
723+ auto temp(*boost::end(insertedRegion));
724+ temp.offsetInLine += insertedLength;
725+ insertedRegion = Region(*boost::const_begin(insertedRegion), temp);
726+ }
727+ fireDocumentAboutToBeChanged(DocumentChange(region, insertedRegion));
728+
721729 try {
722730 // 3. insert allocated strings
723731 if(!allocatedLines.empty())
724732 lines_.insert(std::begin(lines_) + kernel::line(end) + 1, std::begin(allocatedLines), std::end(allocatedLines));
733+
725734 // 4. replace first line
726735 Line& firstLine = *lines_[kernel::line(beginning)];
727736 const Index erasedLength = firstLine.text().length() - offsetInLine(beginning);
728- const Index insertedLength = firstNewline - text.cbegin();
729737 try {
730738 if(!allocatedLines.empty())
731739 firstLine.text_.replace(offsetInLine(beginning), erasedLength, text.cbegin(), insertedLength);
@@ -735,7 +743,6 @@
735743 const Line& lastLine = *lines_[kernel::line(end)];
736744 temp.append(lastLine.text(), offsetInLine(end), lastLine.text().length() - offsetInLine(end));
737745 firstLine.text_.replace(offsetInLine(beginning), erasedLength, temp);
738- endOfInsertedString.offsetInLine += insertedLength;
739746 }
740747 } catch(...) {
741748 const ascension::detail::GapVector<Line*>::const_iterator b(std::begin(lines_) + kernel::line(end) + 1);
@@ -753,6 +760,7 @@
753760 delete line;
754761 throw;
755762 }
763+
756764 // 5. remove lines to erase
757765 if(!boost::empty(region)) {
758766 const auto b(std::begin(lines_) + kernel::line(beginning) + 1), e(std::begin(lines_) + kernel::line(end) + 1);
@@ -770,11 +778,11 @@
770778 if(isRecordingChanges()) {
771779 std::unique_ptr<AtomicChange> c;
772780 if(boost::empty(region))
773- c.reset(new DeletionChange(Region(beginning, endOfInsertedString)));
781+ c.reset(new DeletionChange(insertedRegion));
774782 else if(text.cbegin() == nullptr || text.empty())
775783 c.reset(new InsertionChange(beginning, erasedString.str()));
776784 else
777- c.reset(new ReplacementChange(Region(beginning, endOfInsertedString), erasedString.str()));
785+ c.reset(new ReplacementChange(insertedRegion, erasedString.str()));
778786 undoManager_->addUndoableChange(std::move(c));
779787 }
780788 const bool modified = isModified();
@@ -782,12 +790,12 @@
782790 length_ += insertedStringLength;
783791 length_ -= erasedStringLength;
784792
785- const DocumentChange change(region, Region(beginning, endOfInsertedString));
793+ const DocumentChange change(region, insertedRegion);
786794 fireDocumentChanged(change);
787795 if(!rollbacking_ && !modified)
788796 modificationSignChangedSignal_(*this);
789797
790- return endOfInsertedString;
798+ return *boost::const_end(insertedRegion);
791799 }
792800
793801 /**
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/presentation/presentation.cpp
--- a/ascension/src/presentation/presentation.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/presentation/presentation.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -491,7 +491,7 @@
491491 }
492492
493493 /// @see kernel#DocumentListener#documentAboutToBeChanged
494- void Presentation::documentAboutToBeChanged(const kernel::Document& document) {
494+ void Presentation::documentAboutToBeChanged(const kernel::Document&, const kernel::DocumentChange&) {
495495 // TODO: not implemented.
496496 }
497497
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/rules/lexical-partitioner.cpp
--- a/ascension/src/rules/lexical-partitioner.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/rules/lexical-partitioner.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -40,7 +40,7 @@
4040 }
4141
4242 /// @see kernel#DocumentPartitioner#documentAboutToBeChanged
43- void LexicalPartitioner::documentAboutToBeChanged() BOOST_NOEXCEPT {
43+ void LexicalPartitioner::documentAboutToBeChanged(const kernel::DocumentChange&) BOOST_NOEXCEPT {
4444 }
4545
4646 /// @see kernel#DocumentPartitioner#documentChanged
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/viewer/caret.cpp
--- a/ascension/src/viewer/caret.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/viewer/caret.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -26,6 +26,7 @@
2626 #include <ascension/viewer/text-viewer-model-conversion.hpp>
2727 #include <ascension/viewer/text-viewer-utility.hpp>
2828 #include <ascension/viewer/virtual-box.hpp>
29+#include <ascension/viewer/visual-locations.hpp>
2930 #include <boost/range/algorithm/binary_search.hpp>
3031
3132 namespace ascension {
@@ -36,8 +37,7 @@
3637 }
3738
3839 /// @internal Default constructor.
39- Caret::Context::Context() BOOST_NOEXCEPT : yanking(false), leaveAnchorNext(false), leadingAnchor(false),
40- typing(false), inputMethodCompositionActivated(false), inputMethodComposingCharacter(false) {
40+ Caret::Context::Context() BOOST_NOEXCEPT : yanking(false), typing(false), inputMethodCompositionActivated(false), inputMethodComposingCharacter(false) {
4141 }
4242
4343 // TODO: rewrite this documentation.
@@ -122,8 +122,7 @@
122122 * @param position The initial position of the caret
123123 * @throw kernel#BadPositionException The constructor of @c kernel#Point class threw this exception
124124 */
125- Caret::Caret(kernel::Document& document, const TextHit& position /* = TextHit::leading(kernel::Position::zero()) */) :
126- VisualPoint(document, position), anchor_(new SelectionAnchor(document, insertionPosition(document, position))),
125+ Caret::Caret(kernel::Document& document, const TextHit& position /* = TextHit::leading(kernel::Position::zero()) */) : VisualPoint(document, position),
127126 #if BOOST_OS_WINDOWS
128127 clipboardLocale_(::GetUserDefaultLCID()),
129128 #endif // BOOST_OS_WINDOWS
@@ -136,12 +135,7 @@
136135 * @param other The point used to initialize kernel part of the new object
137136 * @throw kernel#BadPositionException The constructor of @c kernel#Point class threw this exception
138137 */
139- Caret::Caret(const graphics::font::TextHit<kernel::Point>& other) :
140- VisualPoint(other),
141- anchor_(new SelectionAnchor(
142- const_cast<kernel::Document&>(other.characterIndex().document()),
143- insertionPosition(other.characterIndex().document(),
144- other.isLeadingEdge() ? TextHit::leading(other.characterIndex().position()) : TextHit::trailing(other.characterIndex().position())))),
138+ Caret::Caret(const graphics::font::TextHit<kernel::Point>& other) : VisualPoint(other),
145139 #if BOOST_OS_WINDOWS
146140 clipboardLocale_(::GetUserDefaultLCID()),
147141 #endif // BOOST_OS_WINDOWS
@@ -154,8 +148,7 @@
154148 * @param other The point used to initialize kernel part of the new object
155149 * @throw kernel#BadPositionException The constructor of @c kernel#Point class threw this exception
156150 */
157- Caret::Caret(const VisualPoint& other) :
158- VisualPoint(other), anchor_(new SelectionAnchor(other)),
151+ Caret::Caret(const VisualPoint& other) : VisualPoint(other),
159152 #if BOOST_OS_WINDOWS
160153 clipboardLocale_(::GetUserDefaultLCID()),
161154 #endif // BOOST_OS_WINDOWS
@@ -169,8 +162,7 @@
169162 * @param position The initial position of the point
170163 * @throw kernel#BadPositionException The constructor of @c kernel#Point class threw this exception
171164 */
172- Caret::Caret(TextArea& textArea, const TextHit& position /* = TextHit::leading(kernel::Position::zero()) */) :
173- VisualPoint(textArea, position), anchor_(new SelectionAnchor(textArea, position.characterIndex())),
165+ Caret::Caret(TextArea& textArea, const TextHit& position /* = TextHit::leading(kernel::Position::zero()) */) : VisualPoint(textArea, position),
174166 #if BOOST_OS_WINDOWS
175167 clipboardLocale_(::GetUserDefaultLCID()),
176168 #endif // BOOST_OS_WINDOWS
@@ -255,7 +247,6 @@
255247 /// Clears the selection. The anchor will move to the caret.
256248 void Caret::clearSelection() {
257249 endRectangleSelection();
258- context_.leaveAnchorNext = false;
259250 moveTo(hit());
260251 }
261252
@@ -284,25 +275,27 @@
284275 }
285276
286277 /// @see kernel#DocumentListener#documentAboutToBeChanged
287- void Caret::documentAboutToBeChanged(const kernel::Document&) {
278+ void Caret::documentAboutToBeChanged(const kernel::Document&, const kernel::DocumentChange&) {
288279 // does nothing
289280 }
290281
282+ /// @see VisualPoint#documentAboutToBeChanged
283+ void Caret::documentAboutToBeChanged(const kernel::DocumentChange& change) {
284+ assert(context_.anchorDestination == boost::none);
285+ if(anchor_ != boost::none)
286+ context_.anchorDestination = locations::updateTextHit(boost::get(anchor_), document(), change, gravity());
287+ VisualPoint::documentAboutToBeChanged(change);
288+ }
289+
291290 /// @see VisualPoint#documentChanged
292291 void Caret::documentChanged(const kernel::DocumentChange& change) {
293- // notify the movement of the anchor and the caret concurrently when the document was changed
294- context_.leaveAnchorNext = context_.leadingAnchor = true;
295- anchor_->beginInternalUpdate(change);
296292 VisualPoint::documentChanged(change);
297- anchor_->endInternalUpdate();
298- context_.leaveAnchorNext = context_.leadingAnchor = false;
299293 }
300294
301295 /// @see kernel#DocumentListener#documentChanged
302296 void Caret::documentChanged(const kernel::Document&, const kernel::DocumentChange&) {
303297 context_.yanking = false;
304- if(context_.regionBeforeMoved != boost::none)
305- updateVisualAttributes();
298+ updateVisualAttributes();
306299 }
307300
308301 /**
@@ -401,14 +394,14 @@
401394 * @param to The destination position
402395 */
403396 void Caret::extendSelectionTo(const TextHit& to) {
404- context_.leaveAnchorNext = true;
397+ context_.anchorDestination = anchor();
405398 try {
406399 moveTo(to);
407400 } catch(...) {
408- context_.leaveAnchorNext = false;
401+ context_.anchorDestination = boost::none;
409402 throw;
410403 }
411- context_.leaveAnchorNext = false;
404+ context_.anchorDestination = boost::none;
412405 }
413406
414407 /**
@@ -416,14 +409,14 @@
416409 * @param to The destination position
417410 */
418411 void Caret::extendSelectionTo(const VisualDestinationProxy& to) {
419- context_.leaveAnchorNext = true;
412+ context_.anchorDestination = anchor();
420413 try {
421414 moveTo(to);
422415 } catch(...) {
423- context_.leaveAnchorNext = false;
416+ context_.anchorDestination = boost::none;
424417 throw;
425418 }
426- context_.leaveAnchorNext = false;
419+ context_.anchorDestination = boost::none;
427420 }
428421
429422 /// @internal Invokes @c MotionSignal.
@@ -562,11 +555,6 @@
562555 void Caret::install(TextArea& textArea) {
563556 const bool installed = isInstalled();
564557 VisualPoint::install(textArea);
565- if(!installed) {
566- anchor_->install(textArea);
567- anchorMotionSignalConnection_ = anchor_->motionSignal().connect(
568- std::bind(&Caret::pointMoved, this, std::placeholders::_1, std::placeholders::_2));
569- }
570558 setPainter(std::unique_ptr<CaretPainter>());
571559 show();
572560 }
@@ -583,18 +571,13 @@
583571
584572 /// @see VisualPoint#moved
585573 void Caret::moved(const TextHit& from) BOOST_NOEXCEPT {
586- context_.regionBeforeMoved = SelectedRegion(
587- _document = document(),
588- _anchor = anchor_->isInternalUpdating() ? anchor_->positionBeforeInternalUpdate() : insertionPosition(*anchor_),
589- _caret = from);
590- if(context_.leaveAnchorNext)
591- context_.leaveAnchorNext = false;
592- else {
593- context_.leadingAnchor = true;
594- anchor_->moveTo(hit());
595- context_.leadingAnchor = false;
596- }
574+ const SelectedRegion selectionBeforeMotion(_document = document(), _anchor = insertionPosition(document(), anchor()), _caret = from);
597575 VisualPoint::moved(from);
576+ anchor_ = context_.anchorDestination;
577+ context_.anchorDestination = boost::none;
578+ if(anchor_ != boost::none && boost::get(anchor_) == hit())
579+ anchor_ = boost::none;
580+ fireCaretMoved(selectionBeforeMotion);
598581 if(!document().isChanging())
599582 updateVisualAttributes();
600583 }
@@ -612,18 +595,6 @@
612595 }
613596 }
614597
615- /// @see VisualPoint#MotionSignal
616- void Caret::pointMoved(const VisualPoint& self, const TextHit& hitBeforeMotion) {
617- assert(&self == &*anchor_);
618- context_.yanking = false;
619- if(context_.leadingAnchor) // calling anchor_->moveTo in this->moved
620- return;
621- const auto insertionPositionBeforeMotion(insertionPosition(document(), hitBeforeMotion));
622- if((insertionPositionBeforeMotion == insertionPosition(document(), hit())) != isSelectionEmpty(*this))
623- checkMatchBrackets();
624- fireCaretMoved(SelectedRegion(_document = document(), _anchor = insertionPositionBeforeMotion, _caret = hit()));
625- }
626-
627598 /// @internal Should be called before change the document.
628599 inline void Caret::prechangeDocument() {
629600 if(context_.lastTypedPosition && !context_.typing) {
@@ -739,25 +710,10 @@
739710 else if(!encompasses(document().region(), static_cast<const kernel::Region&>(region)))
740711 throw kernel::BadRegionException(region);
741712 context_.yanking = false;
742- if(region.anchor() != insertionPosition(*anchor_) || region.caret() != hit()) {
743- const SelectedRegion oldRegion(selectedRegion());
744- context_.leadingAnchor = true;
745- anchor_->moveTo(TextHit::leading(region.anchor()));
746- context_.leadingAnchor = false;
747- context_.leaveAnchorNext = true;
748- try {
749- VisualPoint::moveTo(region.caret()); // TODO: this may throw...
750- } catch(...) {
751- context_.leaveAnchorNext = false;
752- throw;
753- }
754- if(isSelectionRectangle())
755- context_.selectedRectangle->update(selectedRegion());
756- if(autoShow_)
757- utils::show(*this);
758- fireCaretMoved(oldRegion);
713+ if(region.anchor() != insertionPosition(document(), anchor()) || region.caret() != hit()) {
714+ context_.anchorDestination = TextHit::leading(region.anchor());
715+ VisualPoint::moveTo(region.caret()); // TODO: this may throw...
759716 }
760- checkMatchBrackets();
761717 }
762718
763719 /**
@@ -774,7 +730,7 @@
774730
775731 /// @internal
776732 SelectedRegion Caret::selection() const BOOST_NOEXCEPT {
777- return SelectedRegion(_document = document(), _anchor = insertionPosition(anchor()), _caret = hit());
733+ return SelectedRegion(_document = document(), _anchor = insertionPosition(document(), anchor()), _caret = hit());
778734 }
779735
780736 /// Returns the @c SelectionShapeChangedSignal signal connector.
@@ -825,8 +781,6 @@
825781 void Caret::uninstall() BOOST_NOEXCEPT {
826782 try {
827783 painter_.reset();
828- anchor_->uninstall();
829- anchorMotionSignalConnection_.disconnect();
830784 shapeCache_.image.reset();
831785 context_.Context::Context();
832786 } catch(...) {
@@ -873,52 +827,9 @@
873827 inline void Caret::updateVisualAttributes() {
874828 if(isSelectionRectangle())
875829 context_.selectedRectangle->update(selectedRegion());
876- if(context_.regionBeforeMoved != boost::none
877- && (context_.regionBeforeMoved->anchor() != insertionPosition(document(), hit()) || context_.regionBeforeMoved->caret() != hit()))
878- fireCaretMoved(*context_.regionBeforeMoved);
879830 if(autoShow_)
880831 utils::show(*this);
881832 checkMatchBrackets();
882- context_.regionBeforeMoved = boost::none;
883- }
884-
885-
886- Caret::SelectionAnchor::SelectionAnchor(kernel::Document& document, const kernel::Position& position) : VisualPoint(document, TextHit::leading(position)) {
887- adaptToDocument(false);
888- }
889-
890- Caret::SelectionAnchor::SelectionAnchor(const kernel::Point& other) : VisualPoint(graphics::font::makeLeadingTextHit(other)) {
891- adaptToDocument(false);
892- }
893-
894- Caret::SelectionAnchor::SelectionAnchor(const VisualPoint& other) : VisualPoint(other) {
895- adaptToDocument(false);
896- }
897-
898- Caret::SelectionAnchor::SelectionAnchor(TextArea& textArea, const kernel::Position& position) : VisualPoint(textArea, TextHit::leading(position)) {
899- adaptToDocument(false);
900- }
901-
902- inline void Caret::SelectionAnchor::beginInternalUpdate(const kernel::DocumentChange& change) BOOST_NOEXCEPT {
903- assert(!isInternalUpdating());
904- positionBeforeUpdate_ = insertionPosition(*this);
905- adaptToDocument(true);
906- documentChanged(change);
907- adaptToDocument(false);
908- }
909-
910- inline void Caret::SelectionAnchor::endInternalUpdate() BOOST_NOEXCEPT {
911- assert(isInternalUpdating());
912- positionBeforeUpdate_ = boost::none;
913- }
914-
915- inline bool Caret::SelectionAnchor::isInternalUpdating() const BOOST_NOEXCEPT {
916- return positionBeforeUpdate_ != boost::none;
917- }
918-
919- inline const kernel::Position& Caret::SelectionAnchor::positionBeforeInternalUpdate() const BOOST_NOEXCEPT {
920- assert(isInternalUpdating());
921- return *positionBeforeUpdate_;
922833 }
923834 }
924835 }
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/viewer/text-area.cpp
--- a/ascension/src/viewer/text-area.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/viewer/text-area.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -292,7 +292,7 @@
292292 }
293293
294294 /// @see kernel#DocumentListener#documentAboutToBeChanged
295- void TextArea::documentAboutToBeChanged(const kernel::Document&) {
295+ void TextArea::documentAboutToBeChanged(const kernel::Document&, const kernel::DocumentChange& change) {
296296 // does nothing
297297 }
298298
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/viewer/text-viewer-windows.cpp
--- a/ascension/src/viewer/text-viewer-windows.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/viewer/text-viewer-windows.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -154,7 +154,7 @@
154154
155155 private:
156156 // DocumentListener
157- void documentAboutToBeChanged(const kernel::Document& document) override;
157+ void documentAboutToBeChanged(const kernel::Document& document, const kernel::DocumentChange& change) override;
158158 void documentChanged(const kernel::Document& document, const kernel::DocumentChange& change) override;
159159 private:
160160 TextViewer& viewer_;
@@ -243,7 +243,7 @@
243243 }
244244
245245 /// @see DocumentListener#documentAboutToBeChanged
246- void AccessibleProxy::documentAboutToBeChanged(const kernel::Document&) {
246+ void AccessibleProxy::documentAboutToBeChanged(const kernel::Document&, const kernel::DocumentChange&) {
247247 // do nothing
248248 }
249249
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/viewer/visual-locations.cpp
--- a/ascension/src/viewer/visual-locations.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/viewer/visual-locations.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -477,6 +477,126 @@
477477 return (defaultUIReadingDirection(p) == LEFT_TO_RIGHT) ? forwardWordEnd(p, words) : backwardWordEnd(p, words);
478478 }
479479 #endif // ASCENSION_ABANDONED_AT_VERSION_08
480+
481+ /**
482+ * Adapts the specified @c TextHit to the document change.
483+ *
484+ * <h3>Insertion</h3>
485+ * When "DEF" is inserted between "abc" and "ghi" (underlined is the character-index and '|' is the
486+ * insertion-index):
487+ * <table>
488+ * <tr><th>Case</th><th>@a gravity</th><th>Before</th><th>After</th></tr>
489+ * <tr>
490+ * <td>(I-1)</td><td>Any</td>
491+ * <td><code>a b<span style="text-decoration:underline">|c</span> g h i</code></td>
492+ * <td><code>a b<span style="text-decoration:underline">|c</span> D E F g h i</code></td>
493+ * </tr>
494+ * <tr>
495+ * <td>(I-2a)</td><td>@c Direction#forward()</td>
496+ * <td><code>a b <span style="text-decoration:underline">c|</span>g h i</code></td>
497+ * <td><code>a b c D E <span style="text-decoration:underline">F|</span>g h i</code></td>
498+ * </tr>
499+ * <tr>
500+ * <td>(I-2b)</td><td>@c Direction#backward()</td>
501+ * <td><code>a b <span style="text-decoration:underline">c|</span>g h i</code></td>
502+ * <td><code>a b <span style="text-decoration:underline">c|</span>D E F g h i</code></td>
503+ * </tr>
504+ * <tr>
505+ * <td>(I-3a)</td><td>@c Direction#forward()</td>
506+ * <td><code>a b c<span style="text-decoration:underline">|g</span> h i</code></td>
507+ * <td><code>a b c D E F<span style="text-decoration:underline">|g</span> h i</code></td>
508+ * </tr>
509+ * <tr>
510+ * <td>(I-3b)</td><td>@c Direction#backward()</td>
511+ * <td><code>a b c<span style="text-decoration:underline">|g</span> h i</code></td>
512+ * <td><code>a b c<span style="text-decoration:underline">|D</span> E F g h i</code></td>
513+ * </tr>
514+ * <tr>
515+ * <td>(I-4)</td><td>Any</td>
516+ * <td><code>a b c <span style="text-decoration:underline">g|</span>h i</code></td>
517+ * <td><code>a b c D E F <span style="text-decoration:underline">g|</span>h i</code></td>
518+ * </tr>
519+ * </table>
520+ *
521+ * <h3>Deletion</h3>
522+ * When "DEF" is erased from "abcDEFghi" (underlined is the character-index and '|' is the
523+ * insertion-index):
524+ * <table>
525+ * <tr><th>Case</th><th>@a gravity</th><th>Before</th><th>After</th></tr>
526+ * <tr>
527+ * <td>(D-1)</td><td>Any</td>
528+ * <td><code>a b <span style="text-decoration:underline">c|</span>D E F g h i</code></td>
529+ * <td><code>a b <span style="text-decoration:underline">c|</span>g h i</code></td>
530+ * </tr>
531+ * <tr>
532+ * <td>(D-2a)</td><td>Any</td>
533+ * <td><code>a b c<span style="text-decoration:underline">|D</span> E F g h i</code></td>
534+ * <td><code>a b c<span style="text-decoration:underline">|g</span> h i</code></td>
535+ * </tr>
536+ * <tr>
537+ * <td>(D-2b)</td><td>Any</td>
538+ * <td><code>a b c <span style="text-decoration:underline">D|</span>E F g h i</code></td>
539+ * <td><code>a b c<span style="text-decoration:underline">|g</span> h i</code></td>
540+ * </tr>
541+ * <tr>
542+ * <td>(D-2c)</td><td>Any</td>
543+ * <td><code>a b c D<span style="text-decoration:underline">|E</span> F g h i</code></td>
544+ * <td><code>a b c<span style="text-decoration:underline">|g</span> h i</code></td>
545+ * </tr>
546+ * <tr>
547+ * <td>(D-3)</td><td>Any</td>
548+ * <td><code>a b c D E <span style="text-decoration:underline">F|</span>g h i</code></td>
549+ * <td><code>a b <span style="text-decoration:underline">c|</span>g h i</code></td>
550+ * </tr>
551+ * <tr>
552+ * <td>(D-4)</td><td>Any</td>
553+ * <td><code>a b c D E F<span style="text-decoration:underline">|g</span> h i</code></td>
554+ * <td><code>a b c<span style="text-decoration:underline">|g</span> h i</code></td>
555+ * </tr>
556+ * </table>
557+ *
558+ * @param hit The original text hit
559+ * @param document The document
560+ * @param change The content of the document change
561+ * @param gravity See @c kernel#locations#updatePosition method and tables above
562+ * @return The result text hit
563+ * @throw ... Any exception @c kernel#DocumentCharacterIterator throws
564+ * @note This function creates and uses @c kernel#DocumentCharacterIterator instance. After @a change is
565+ * processed by @a document, some exception may occur.
566+ * @see kernel#locations#updatePosition
567+ */
568+ TextHit updateTextHit(const TextHit& hit, const kernel::Document& document, const kernel::DocumentChange& change, Direction gravity) {
569+ TextHit h(hit);
570+ if(!boost::empty(change.erasedRegion())) { // deletion
571+ assert(*boost::const_begin(change.erasedRegion()) <= *boost::const_end(change.erasedRegion()));
572+ const auto& b = *boost::const_begin(change.erasedRegion()), e = *boost::const_end(change.erasedRegion());
573+ if(h.characterIndex() >= b && h.characterIndex() < e) { // (D-2) or (D-3)
574+ if(insertionPosition(document, h) < e) // (D-2)
575+ h = TextHit::leading(e);
576+ else { // (D-3)
577+ kernel::DocumentCharacterIterator i(document, b);
578+ h = TextHit::trailing((--i).tell());
579+ }
580+ } else { // (D-1) or (D-4)
581+ const auto p(kernel::locations::detail::updatePositionForDeletion(h.characterIndex(), change.erasedRegion(), gravity));
582+ h = h.isLeadingEdge() ? TextHit::leading(p) : TextHit::trailing(p);
583+ }
584+ }
585+ if(!boost::empty(change.insertedRegion())) { // insertion
586+ assert(*boost::const_begin(change.insertedRegion()) <= *boost::const_end(change.insertedRegion()));
587+ const auto& b = *boost::const_begin(change.insertedRegion()), e = *boost::const_end(change.insertedRegion());
588+ const auto ip(insertionPosition(document, h));
589+ if(ip == b) { // (I-2) or (I-3)
590+ h = TextHit::leading((gravity == Direction::forward()) ? e : b);
591+ if(!h.isLeadingEdge()) // (I-2)
592+ h = otherHit(document, h);
593+ } else { // (I-1) or (I-4)
594+ const auto p(kernel::locations::detail::updatePositionForInsertion(h.characterIndex(), change.insertedRegion(), gravity));
595+ h = h.isLeadingEdge() ? TextHit::leading(p) : TextHit::trailing(p);
596+ }
597+ }
598+ return h;
599+ }
480600 }
481601 }
482602 }
diff -r f081ed7ffd4c -r 7668a9fffb7f ascension/src/viewer/visual-point.cpp
--- a/ascension/src/viewer/visual-point.cpp Sun Oct 22 10:52:15 2017 +0900
+++ b/ascension/src/viewer/visual-point.cpp Sun Oct 22 23:05:54 2017 +0900
@@ -279,16 +279,24 @@
279279 moveTo(TextHit::leading(kernel::Position::zero()));
280280 }
281281
282+ /// @see AbstractPoint#documentAboutToBeChanged
283+ void VisualPoint::documentAboutToBeChanged(const kernel::DocumentChange& change) {
284+ assert(!isDocumentDisposed());
285+ assert(adaptsToDocument());
286+ assert(hitAfterChange_ == boost::none);
287+ hitAfterChange_ = locations::updateTextHit(hit(), document(), change, gravity());
288+ }
289+
282290 /// @see AbstractPoint#documentChanged
283291 void VisualPoint::documentChanged(const kernel::DocumentChange& change) {
284292 assert(!isDocumentDisposed());
285293 assert(adaptsToDocument());
286-// normalize();
287- const auto ip(insertionPosition(*this));
288- const auto newPosition(kernel::positions::updatePosition(ip, change, gravity()));
289- const TextHit newHit((hit().isLeadingEdge() || includes(change.erasedRegion(), ip)) ? TextHit::leading(newPosition) : TextHit::trailing(newPosition));
290- if(newHit != hit())
291- moveTo(newHit); // TODO: this may throw...
294+ if(hitAfterChange_ != boost::none) {
295+ const auto newHit(boost::get(hitAfterChange_));
296+ hitAfterChange_ = boost::none;
297+ if(newHit != hit())
298+ moveTo(newHit); // TODO: this may throw...
299+ }
292300 }
293301
294302 /**
Show on old repository browser