mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-11-11 05:33:33 +00:00
038cc70650
introduce 'semantic' TeX commands \lyxinserted and \lyxdeleted in order to decouple change tracking output from dvipost. Raise user warning if dvipost is not installed (i.e. no changes are shown in TeX output) git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@18237 a592a061-630c-0410-9148-cb99ea01b6c8
361 lines
8.1 KiB
C++
361 lines
8.1 KiB
C++
/**
|
|
* \file Changes.cpp
|
|
* This file is part of LyX, the document processor.
|
|
* Licence details can be found in the file COPYING.
|
|
*
|
|
* \author John Levon
|
|
* \author Michael Gerz
|
|
*
|
|
* Full author contact details are available in file CREDITS.
|
|
*
|
|
* Record changes in a paragraph.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "Changes.h"
|
|
#include "debug.h"
|
|
#include "Author.h"
|
|
#include "BufferParams.h"
|
|
#include "LaTeXFeatures.h"
|
|
|
|
#include <boost/assert.hpp>
|
|
|
|
|
|
namespace lyx {
|
|
|
|
using std::abs;
|
|
using std::endl;
|
|
using std::string;
|
|
using std::max;
|
|
|
|
/*
|
|
* Class Change has a changetime field that specifies the exact time at which
|
|
* a specific change was made. The change time is used as a guidance for the
|
|
* user while editing his document. Presently, it is not considered for LaTeX
|
|
* export.
|
|
* When merging two adjacent changes, the changetime is not considered,
|
|
* only the equality of the change type and author is checked (in method
|
|
* isSimilarTo(...)). If two changes are in fact merged (in method merge()),
|
|
* the later change time is preserved.
|
|
*/
|
|
|
|
bool Change::isSimilarTo(Change const & change)
|
|
{
|
|
if (type != change.type) {
|
|
return false;
|
|
}
|
|
|
|
if (type == Change::UNCHANGED) {
|
|
return true;
|
|
}
|
|
|
|
return author == change.author;
|
|
}
|
|
|
|
|
|
bool operator==(Change const & l, Change const & r)
|
|
{
|
|
if (l.type != r.type) {
|
|
return false;
|
|
}
|
|
|
|
// two changes of type UNCHANGED are always equal
|
|
if (l.type == Change::UNCHANGED) {
|
|
return true;
|
|
}
|
|
|
|
return l.author == r.author &&
|
|
l.changetime == r.changetime;
|
|
}
|
|
|
|
|
|
bool operator!=(Change const & l, Change const & r)
|
|
{
|
|
return !(l == r);
|
|
}
|
|
|
|
|
|
bool operator==(Changes::Range const & r1, Changes::Range const & r2)
|
|
{
|
|
return r1.start == r2.start && r1.end == r2.end;
|
|
}
|
|
|
|
|
|
bool operator!=(Changes::Range const & r1, Changes::Range const & r2)
|
|
{
|
|
return !(r1 == r2);
|
|
}
|
|
|
|
|
|
bool Changes::Range::intersects(Range const & r) const
|
|
{
|
|
return r.start < end && r.end > start; // end itself is not in the range!
|
|
}
|
|
|
|
|
|
void Changes::set(Change const & change, pos_type const pos)
|
|
{
|
|
set(change, pos, pos + 1);
|
|
}
|
|
|
|
|
|
void Changes::set(Change const & change, pos_type const start, pos_type const end)
|
|
{
|
|
if (change.type != Change::UNCHANGED) {
|
|
LYXERR(Debug::CHANGES) << "setting change (type: " << change.type
|
|
<< ", author: " << change.author << ", time: " << change.changetime
|
|
<< ") in range (" << start << ", " << end << ")" << endl;
|
|
}
|
|
|
|
Range const newRange(start, end);
|
|
|
|
ChangeTable::iterator it = table_.begin();
|
|
|
|
for (; it != table_.end(); ) {
|
|
// current change starts like or follows new change
|
|
if (it->range.start >= start) {
|
|
break;
|
|
}
|
|
|
|
// new change intersects with existing change
|
|
if (it->range.end > start) {
|
|
pos_type oldEnd = it->range.end;
|
|
it->range.end = start;
|
|
|
|
LYXERR(Debug::CHANGES) << " cutting tail of type " << it->change.type
|
|
<< " resulting in range (" << it->range.start << ", "
|
|
<< it->range.end << ")" << endl;
|
|
|
|
++it;
|
|
if (oldEnd >= end) {
|
|
LYXERR(Debug::CHANGES) << " inserting tail in range ("
|
|
<< end << ", " << oldEnd << ")" << endl;
|
|
it = table_.insert(it, ChangeRange((it-1)->change, Range(end, oldEnd)));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
++it;
|
|
}
|
|
|
|
if (change.type != Change::UNCHANGED) {
|
|
LYXERR(Debug::CHANGES) << " inserting change" << endl;
|
|
it = table_.insert(it, ChangeRange(change, Range(start, end)));
|
|
++it;
|
|
}
|
|
|
|
for (; it != table_.end(); ) {
|
|
// new change 'contains' existing change
|
|
if (newRange.contains(it->range)) {
|
|
LYXERR(Debug::CHANGES) << " removing subrange ("
|
|
<< it->range.start << ", " << it->range.end << ")" << endl;
|
|
it = table_.erase(it);
|
|
continue;
|
|
}
|
|
|
|
// new change precedes existing change
|
|
if (it->range.start >= end) {
|
|
break;
|
|
}
|
|
|
|
// new change intersects with existing change
|
|
it->range.start = end;
|
|
LYXERR(Debug::CHANGES) << " cutting head of type "
|
|
<< it->change.type << " resulting in range ("
|
|
<< end << ", " << it->range.end << ")" << endl;
|
|
break; // no need for another iteration
|
|
}
|
|
|
|
merge();
|
|
}
|
|
|
|
|
|
void Changes::erase(pos_type const pos)
|
|
{
|
|
LYXERR(Debug::CHANGES) << "Erasing change at position " << pos << endl;
|
|
|
|
ChangeTable::iterator it = table_.begin();
|
|
ChangeTable::iterator end = table_.end();
|
|
|
|
for (; it != end; ++it) {
|
|
// range (pos,pos+x) becomes (pos,pos+x-1)
|
|
if (it->range.start > pos) {
|
|
--(it->range.start);
|
|
}
|
|
// range (pos-x,pos) stays (pos-x,pos)
|
|
if (it->range.end > pos) {
|
|
--(it->range.end);
|
|
}
|
|
}
|
|
|
|
merge();
|
|
}
|
|
|
|
|
|
void Changes::insert(Change const & change, lyx::pos_type pos)
|
|
{
|
|
if (change.type != Change::UNCHANGED) {
|
|
LYXERR(Debug::CHANGES) << "Inserting change of type " << change.type
|
|
<< " at position " << pos << endl;
|
|
}
|
|
|
|
ChangeTable::iterator it = table_.begin();
|
|
ChangeTable::iterator end = table_.end();
|
|
|
|
for (; it != end; ++it) {
|
|
// range (pos,pos+x) becomes (pos+1,pos+x+1)
|
|
if (it->range.start >= pos) {
|
|
++(it->range.start);
|
|
}
|
|
|
|
// range (pos-x,pos) stays as it is
|
|
if (it->range.end > pos) {
|
|
++(it->range.end);
|
|
}
|
|
}
|
|
|
|
set(change, pos, pos + 1); // set will call merge
|
|
}
|
|
|
|
|
|
Change const & Changes::lookup(pos_type const pos) const
|
|
{
|
|
static Change const noChange = Change(Change::UNCHANGED);
|
|
|
|
ChangeTable::const_iterator it = table_.begin();
|
|
ChangeTable::const_iterator const end = table_.end();
|
|
|
|
for (; it != end; ++it) {
|
|
if (it->range.contains(pos))
|
|
return it->change;
|
|
}
|
|
|
|
return noChange;
|
|
}
|
|
|
|
|
|
bool Changes::isChanged(pos_type const start, pos_type const end) const
|
|
{
|
|
ChangeTable::const_iterator it = table_.begin();
|
|
ChangeTable::const_iterator const itend = table_.end();
|
|
|
|
for (; it != itend; ++it) {
|
|
if (it->range.intersects(Range(start, end))) {
|
|
LYXERR(Debug::CHANGES) << "found intersection of range ("
|
|
<< start << ", " << end << ") with ("
|
|
<< it->range.start << ", " << it->range.end
|
|
<< ") of type " << it->change.type << endl;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void Changes::merge()
|
|
{
|
|
ChangeTable::iterator it = table_.begin();
|
|
|
|
while (it != table_.end()) {
|
|
LYXERR(Debug::CHANGES) << "found change of type " << it->change.type
|
|
<< " and range (" << it->range.start << ", " << it->range.end
|
|
<< ")" << endl;
|
|
|
|
if (it->range.start == it->range.end) {
|
|
LYXERR(Debug::CHANGES) << "removing empty range for pos "
|
|
<< it->range.start << endl;
|
|
|
|
table_.erase(it);
|
|
// start again
|
|
it = table_.begin();
|
|
continue;
|
|
}
|
|
|
|
if (it + 1 == table_.end())
|
|
break;
|
|
|
|
if (it->change.isSimilarTo((it + 1)->change) && it->range.end == (it + 1)->range.start) {
|
|
LYXERR(Debug::CHANGES) << "merging ranges (" << it->range.start << ", "
|
|
<< it->range.end << ") and (" << (it + 1)->range.start << ", "
|
|
<< (it + 1)->range.end << ")" << endl;
|
|
|
|
(it + 1)->range.start = it->range.start;
|
|
(it + 1)->change.changetime = max(it->change.changetime,
|
|
(it + 1)->change.changetime);
|
|
table_.erase(it);
|
|
// start again
|
|
it = table_.begin();
|
|
continue;
|
|
}
|
|
|
|
++it;
|
|
}
|
|
}
|
|
|
|
|
|
int Changes::latexMarkChange(odocstream & os, BufferParams const & bparams,
|
|
Change const & oldChange, Change const & change)
|
|
{
|
|
if (!bparams.outputChanges || oldChange == change)
|
|
return 0;
|
|
|
|
int column = 0;
|
|
|
|
if (oldChange.type != Change::UNCHANGED) {
|
|
os << '}'; // close \lyxinserted or \lyxdeleted
|
|
column++;
|
|
}
|
|
|
|
docstring chgTime;
|
|
chgTime += ctime(&change.changetime);
|
|
chgTime.erase(chgTime.end() - 1); // remove trailing '\n'
|
|
|
|
if (change.type == Change::DELETED) {
|
|
docstring str = "\\lyxdeleted{" +
|
|
bparams.authors().get(change.author).name() + "}{" +
|
|
chgTime + "}{";
|
|
os << str;
|
|
column += str.size();
|
|
} else if (change.type == Change::INSERTED) {
|
|
docstring str = "\\lyxinserted{" +
|
|
bparams.authors().get(change.author).name() + "}{" +
|
|
chgTime + "}{";
|
|
os << str;
|
|
column += str.size();
|
|
}
|
|
|
|
return column;
|
|
}
|
|
|
|
|
|
void Changes::lyxMarkChange(std::ostream & os, int & column,
|
|
Change const & old, Change const & change)
|
|
{
|
|
if (old == change)
|
|
return;
|
|
|
|
column = 0;
|
|
|
|
switch (change.type) {
|
|
case Change::UNCHANGED:
|
|
os << "\n\\change_unchanged\n";
|
|
break;
|
|
|
|
case Change::DELETED: {
|
|
os << "\n\\change_deleted " << change.author
|
|
<< " " << change.changetime << "\n";
|
|
break;
|
|
}
|
|
|
|
case Change::INSERTED: {
|
|
os << "\n\\change_inserted " << change.author
|
|
<< " " << change.changetime << "\n";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
} // namespace lyx
|