This patch uses both pit and id to locate a bookmark.
1. both pit and id are used to locate a position. Pit is saved by session. 2. moveToPosition now returns id and pit of the new position and update bookmark if necessary. These happens: * when id is invalid (Uwe's case), pit is used, and bookmark id is updated. * when a paragraph is added/removed, id can still locate the paragraph but pit needs to be updated. 3. when a buffer/window is closed, its bookmarks are updated (by going to the bookmarks and get their pit.) This avoids the following problem: set bookmark; go to the begining of the buffer add a few paragraphs (id ok, pit changed); close buffer; open the buffer using the bookmark, bookmark is shifted because id is now invalid, pit is shifted. You can see that I update bookmark when buffer switch, buffer close, windows close, but I still can not update bookmark when File->exit is triggered. WHY CANNOT WE take the time to fire LFUN_BUFFER_CLOSE, and LFUN_WINDOW_CLOSE when lyx exists? When lyx takes shortcuts to quit, proper cleanup in these CLOSE events may be bypassed. Bo
Index: src/session.C =================================================================== --- src/session.C (revision 16638) +++ src/session.C (working copy) @@ -244,12 +244,12 @@ try { // read bookmarks - // id, pos, file\n - unsigned int id; + // pit, pos, file\n + pit_type pit; pos_type pos; string fname; istringstream itmp(tmp); - itmp >> id; + itmp >> pit; itmp.ignore(2); // ignore ", " itmp >> pos; itmp.ignore(2); // ignore ", " @@ -261,7 +261,7 @@ if (fs::exists(file.toFilesystemEncoding()) && !fs::is_directory(file.toFilesystemEncoding()) && bookmarks.size() < max_bookmarks) - bookmarks.push_back(Bookmark(file, id, pos)); + bookmarks.push_back(Bookmark(file, pit, 0, pos)); else lyxerr[Debug::INIT] << "LyX: Warning: Ignore bookmark of file: " << fname << endl; } catch (...) { @@ -275,22 +275,22 @@ { os << '\n' << sec_bookmarks << '\n'; for (size_t i = 0; i < bookmarks.size(); ++i) { - os << bookmarks[i].par_id << ", " + os << bookmarks[i].par_pit << ", " << bookmarks[i].par_pos << ", " << bookmarks[i].filename << '\n'; } } -void BookmarksSection::save(FileName const & fname, int par_id, pos_type par_pos, bool persistent) +void BookmarksSection::save(FileName const & fname, pit_type par_pit, int par_id, pos_type par_pos, bool persistent) { if (persistent) { - bookmarks.push_back(Bookmark(fname, par_id, par_pos)); + bookmarks.push_back(Bookmark(fname, par_pit, par_id, par_pos)); if (bookmarks.size() > max_bookmarks) bookmarks.pop_back(); } else - temp_bookmark = Bookmark(fname, par_id, par_pos); + temp_bookmark = Bookmark(fname, par_pit, par_id, par_pos); } Index: src/lyxfunc.C =================================================================== --- src/lyxfunc.C (revision 16638) +++ src/lyxfunc.C (working copy) @@ -242,6 +242,42 @@ } +void LyXFunc::gotoBookmark(unsigned int idx, bool openFile, bool switchToBuffer) +{ + BOOST_ASSERT(lyx_view_); + if (!LyX::ref().session().bookmarks().isValid(idx)) + return; + BookmarksSection::Bookmark const & bm = LyX::ref().session().bookmarks().bookmark(idx); + BOOST_ASSERT(!bm.filename.empty()); + string const file = bm.filename.absFilename(); + // if the file is not opened, open it. + if (!theBufferList().exists(file)) { + if (openFile) + dispatch(FuncRequest(LFUN_FILE_OPEN, file)); + else + return; + } + // open may fail, so we need to test it again + if (theBufferList().exists(file)) { + // if the current buffer is not that one, switch to it. + if (lyx_view_->buffer()->fileName() != file) { + if (switchToBuffer) + dispatch(FuncRequest(LFUN_BUFFER_SWITCH, file)); + else + return; + } + // moveToPosition use par_id, and par_pit and return new par_id. + pit_type new_pit; + int new_id; + boost::tie(new_pit, new_id) = view()->moveToPosition(bm.par_pit, bm.par_id, bm.par_pos); + // if par_id or pit has been changed, reset par_id + // see http://bugzilla.lyx.org/show_bug.cgi?id=3092 + if (bm.par_pit != new_pit || bm.par_id != new_id) + const_cast<BookmarksSection::Bookmark &>(bm).setPos(new_pit, new_id); + } +} + + void LyXFunc::processKeySym(LyXKeySymPtr keysym, key_modifier::state state) { lyxerr[Debug::KEY] << "KeySym is " << keysym->getSymbolName() << endl; @@ -1126,6 +1162,9 @@ // --- buffers ---------------------------------------- case LFUN_BUFFER_SWITCH: BOOST_ASSERT(lyx_view_); + // update bookmark pit. + for (size_t i = 0; i < LyX::ref().session().bookmarks().size(); ++i) + gotoBookmark(i+1, false, false); lyx_view_->setBuffer(theBufferList().getBuffer(argument)); break; @@ -1664,33 +1703,19 @@ case LFUN_WINDOW_CLOSE: BOOST_ASSERT(lyx_view_); BOOST_ASSERT(theApp()); + // update bookmark pit. + for (size_t i = 0; i < LyX::ref().session().bookmarks().size(); ++i) + gotoBookmark(i+1, false, false); // ask the user for saving changes or cancel quit if (!theBufferList().quitWriteAll()) break; lyx_view_->close(); return; - case LFUN_BOOKMARK_GOTO: { - BOOST_ASSERT(lyx_view_); - unsigned int idx = convert<unsigned int>(to_utf8(cmd.argument())); - if (!LyX::ref().session().bookmarks().isValid(idx)) - break; - BookmarksSection::Bookmark const bm = LyX::ref().session().bookmarks().bookmark(idx); - BOOST_ASSERT(!bm.filename.empty()); - string const file = bm.filename.absFilename(); - // if the file is not opened, open it. - if (!theBufferList().exists(file)) - dispatch(FuncRequest(LFUN_FILE_OPEN, file)); - // open may fail, so we need to test it again - if (theBufferList().exists(file)) { - // if the current buffer is not that one, switch to it. - if (lyx_view_->buffer()->fileName() != file) - dispatch(FuncRequest(LFUN_BUFFER_SWITCH, file)); - // BOOST_ASSERT(lyx_view_->buffer()->fileName() != file); - view()->moveToPosition(bm.par_id, bm.par_pos); - } + case LFUN_BOOKMARK_GOTO: + // go to bookmark, open unopened file and switch to buffer if necessary + gotoBookmark(convert<unsigned int>(to_utf8(cmd.argument())), true, true); break; - } case LFUN_BOOKMARK_CLEAR: LyX::ref().session().bookmarks().clear(); @@ -2001,6 +2026,9 @@ // save current cursor position LyX::ref().session().lastFilePos().save(FileName(lyx_view_->buffer()->fileName()), boost::tie(view()->cursor().pit(), view()->cursor().pos()) ); + // goto bookmark to update bookmark pit. + for (size_t i = 0; i < LyX::ref().session().bookmarks().size(); ++i) + gotoBookmark(i+1, false, false); if (theBufferList().close(lyx_view_->buffer(), true) && !quitting) { if (theBufferList().empty()) { // need this otherwise SEGV may occur while Index: src/session.h =================================================================== --- src/session.h (revision 16638) +++ src/session.h (working copy) @@ -179,6 +179,8 @@ public: /// Filename support::FileName filename; + /// Cursor pit + pit_type par_pit; /// Cursor paragraph Id int par_id; /// Cursor position @@ -186,8 +188,14 @@ /// Bookmark() : par_id(0), par_pos(0) {} /// - Bookmark(support::FileName const & f, int id, pos_type pos) - : filename(f), par_id(id), par_pos(pos) {} + Bookmark(support::FileName const & f, pit_type pit, int id, pos_type pos) + : filename(f), par_pit(pit), par_id(id), par_pos(pos) {} + /// set bookmark par_id, this is because newly loaded bookmark + /// may have invalid or zero par_id, see bug 3092 + void setPos(pit_type pit, int id) { + par_pit = pit; + par_id = id; + } }; /// @@ -200,7 +208,7 @@ /// Save the current position as bookmark /// if save==false, save to temp_bookmark - void save(support::FileName const & fname, int par_id, pos_type par_pos, bool persistent); + void save(support::FileName const & fname, pit_type pit, int par_id, pos_type par_pos, bool persistent); /// return bookmark, return temp_bookmark if i==0 Bookmark const & bookmark(unsigned int i) const; Index: src/lyxfunc.h =================================================================== --- src/lyxfunc.h (revision 16638) +++ src/lyxfunc.h (working copy) @@ -75,6 +75,11 @@ docstring const getMessage() const { return dispatch_buffer; } /// Handle a accented char key sequence void handleKeyFunc(kb_action action); + /// goto a bookmark + /// openFile: whether or not open a file if the file is not opened + /// switchToBuffer: whether or not switch to buffer if the buffer is + /// not the current buffer + void gotoBookmark(unsigned int idx, bool openFile, bool switchToBuffer); private: /// Index: src/BufferView.C =================================================================== --- src/BufferView.C (revision 16638) +++ src/BufferView.C (working copy) @@ -263,22 +263,10 @@ pit_type pit; pos_type pos; boost::tie(pit, pos) = LyX::ref().session().lastFilePos().load(filename); - // I am not sure how to separate the following part to a function - // so I will leave this to Lars. - // - // check pit since the document may be externally changed. - if ( static_cast<size_t>(pit) < b->paragraphs().size() ) { - ParIterator it = b->par_iterator_begin(); - ParIterator const end = b->par_iterator_end(); - for (; it != end; ++it) - if (it.pit() == pit) { - // restored pos may be bigger than it->size - setCursor(makeDocIterator(it, min(pos, it->size()))); - // No need to update the metrics if fitCursor returns false. - if (fitCursor()) - updateMetrics(false); - break; - } + // if successfully move to pit (returned par_id is not zero), update metrics + if (moveToPosition(pit, 0, pos).get<1>()) { + if (fitCursor()) + updateMetrics(false); } } @@ -535,6 +523,7 @@ { LyX::ref().session().bookmarks().save( FileName(buffer_->fileName()), + cursor_.pit(), cursor_.paragraph().id(), cursor_.pos(), persistent @@ -545,15 +534,31 @@ } -void BufferView::moveToPosition(int par_id, pos_type par_pos) +boost::tuple<pit_type, int> BufferView::moveToPosition(pit_type par_pit, int par_id, pos_type par_pos) { cursor_.clearSelection(); - ParIterator par = buffer_->getParFromID(par_id); - if (par == buffer_->par_iterator_end()) - return; - - setCursor(makeDocIterator(par, min(par->size(), par_pos))); + // if a valid par_id is given, try it first + if (par_id > 0) { + ParIterator par = buffer_->getParFromID(par_id); + if (par != buffer_->par_iterator_end()) { + setCursor(makeDocIterator(par, min(par->size(), par_pos))); + return boost::make_tuple(cursor_.pit(), par_id); + } + } + // if par_id == 0, or searching through par_id failed + if (static_cast<size_t>(par_pit) < buffer_->paragraphs().size()) { + ParIterator it = buffer_->par_iterator_begin(); + ParIterator const end = buffer_->par_iterator_end(); + for (; it != end; ++it) + if (it.pit() == par_pit) { + // restored pos may be bigger than it->size + setCursor(makeDocIterator(it, min(par_pos, it->size()))); + return boost::make_tuple(par_pit, it->id()); + } + } + // both methods fail + return boost::make_tuple(pit_type(0), 0); } Index: src/BufferView.h =================================================================== --- src/BufferView.h (revision 16638) +++ src/BufferView.h (working copy) @@ -23,6 +23,7 @@ #include "support/types.h" +#include <boost/tuple/tuple.hpp> #include <boost/utility.hpp> #include <boost/signal.hpp> @@ -116,8 +117,10 @@ /// Save the current position as bookmark. /// if persistent=false, save to temp_bookmark void saveBookmark(bool persistent); - /// goto a specified position. - void moveToPosition( + /// goto a specified position, try par_id first, and then par_pit + /// return the par_pit and par_id of the new paragraph + boost::tuple<pit_type, int> moveToPosition( + pit_type par_pit, ///< Paragraph pit, used when par_id is zero or invalid. int par_id, ///< Paragraph ID, \sa Paragraph pos_type par_pos ///< Position in the \c Paragraph );