mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-11-24 18:43:37 +00:00
04a3819779
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@9535 a592a061-630c-0410-9148-cb99ea01b6c8
577 lines
12 KiB
C
577 lines
12 KiB
C
/**
|
|
* \file changes.C
|
|
* This file is part of LyX, the document processor.
|
|
* Licence details can be found in the file COPYING.
|
|
*
|
|
* \author John Levon
|
|
*
|
|
* Full author contact details are available in file CREDITS.
|
|
*
|
|
* Record changes in a paragraph.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "changes.h"
|
|
#include "debug.h"
|
|
|
|
#include <boost/assert.hpp>
|
|
|
|
using lyx::pos_type;
|
|
|
|
using std::endl;
|
|
using std::string;
|
|
|
|
|
|
bool operator==(Change const & l, Change const & r)
|
|
{
|
|
return l.type == r.type && 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::contains(Range const & r) const
|
|
{
|
|
return r.start >= start && r.end <= end;
|
|
}
|
|
|
|
|
|
bool Changes::Range::contained(Range const & r) const
|
|
{
|
|
return r.contains(*this);
|
|
}
|
|
|
|
|
|
bool Changes::Range::contains(pos_type const pos) const
|
|
{
|
|
return pos >= start && pos < end;
|
|
}
|
|
|
|
|
|
bool Changes::Range::loose_contains(pos_type const pos) const
|
|
{
|
|
return pos >= start && pos <= end;
|
|
}
|
|
|
|
|
|
bool Changes::Range::intersects(Range const & r) const
|
|
{
|
|
return contained(r) || contains(r)
|
|
|| contains(r.start) || contains(r.end);
|
|
}
|
|
|
|
|
|
Changes::Changes(Change::Type const type)
|
|
: empty_type_(type)
|
|
{
|
|
}
|
|
|
|
|
|
Changes::~Changes()
|
|
{
|
|
}
|
|
|
|
|
|
Changes::Changes(Changes const & c)
|
|
{
|
|
table_ = c.table_;
|
|
}
|
|
|
|
|
|
void Changes::record(Change const change, pos_type const pos)
|
|
{
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "record " << change.type
|
|
<< " at pos " << pos << " with total "
|
|
<< table_.size() << " changes." << endl;
|
|
}
|
|
|
|
switch (change.type) {
|
|
case Change::INSERTED:
|
|
add(change, pos);
|
|
break;
|
|
case Change::DELETED:
|
|
del(change, pos);
|
|
break;
|
|
case Change::UNCHANGED:
|
|
set(Change::UNCHANGED, pos);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void Changes::set(Change const change, pos_type const pos)
|
|
{
|
|
set(change, pos, pos + 1);
|
|
}
|
|
|
|
|
|
void Changes::set(Change::Type const type, pos_type const pos)
|
|
{
|
|
set(type, pos, pos + 1);
|
|
}
|
|
|
|
|
|
void Changes::set(Change::Type const type,
|
|
pos_type const start, pos_type const end)
|
|
{
|
|
set(Change(type), start, end);
|
|
}
|
|
|
|
|
|
void Changes::set(Change const change,
|
|
pos_type const start, pos_type const end)
|
|
{
|
|
ChangeTable::iterator it = table_.begin();
|
|
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "changeset of " << change.type
|
|
<< " author " << change.author << " time " << change.changetime
|
|
<< " in range " << start << "," << end << endl;
|
|
}
|
|
|
|
Range const new_range(start, end);
|
|
|
|
// remove all sub-ranges
|
|
for (; it != table_.end();) {
|
|
if (new_range != it->range && it->range.contained(new_range)) {
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "Removing subrange "
|
|
<< it->range.start << "," << it->range.end << endl;
|
|
}
|
|
it = table_.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
it = table_.begin();
|
|
ChangeTable::iterator const itend = table_.end();
|
|
|
|
// find a super-range
|
|
for (; it != itend; ++it) {
|
|
if (it->range.contains(new_range))
|
|
break;
|
|
}
|
|
|
|
if (it == itend) {
|
|
lyxerr[Debug::CHANGES] << "Inserting change at end" << endl;
|
|
table_.push_back(ChangeRange(start, end, change));
|
|
merge();
|
|
return;
|
|
}
|
|
|
|
if (change.type == it->change.type) {
|
|
lyxerr[Debug::CHANGES] << "Change set already." << endl;
|
|
it->change = change;
|
|
return;
|
|
}
|
|
|
|
ChangeRange c(*it);
|
|
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "Using change of type " << c.change.type
|
|
<< " over " << c.range.start << "," << c.range.end << endl;
|
|
}
|
|
|
|
// split head
|
|
if (c.range.start < start) {
|
|
it = table_.insert(it, ChangeRange(c.range.start, start, c.change));
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "Splitting head of type " << c.change.type
|
|
<< " over " << c.range.start << "," << start << endl;
|
|
}
|
|
++it;
|
|
}
|
|
|
|
// reset this as new type
|
|
it->range.start = start;
|
|
it->range.end = end;
|
|
it->change = change;
|
|
lyxerr[Debug::CHANGES] << "Resetting to new change" << endl;
|
|
|
|
// split tail
|
|
if (c.range.end > end) {
|
|
++it;
|
|
table_.insert(it, ChangeRange(end, c.range.end, c.change));
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "Splitting tail of type " << c.change.type
|
|
<< " over " << end << "," << c.range.end << endl;
|
|
}
|
|
}
|
|
|
|
check();
|
|
merge();
|
|
}
|
|
|
|
|
|
void Changes::erase(pos_type const pos)
|
|
{
|
|
ChangeTable::iterator it = table_.begin();
|
|
ChangeTable::iterator end = table_.end();
|
|
|
|
bool found = false;
|
|
|
|
for (; it != end; ++it) {
|
|
Range & range(it->range);
|
|
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "era:Range of type " << it->change.type << " is "
|
|
<< it->range.start << "," << it->range.end << endl;
|
|
}
|
|
|
|
if (range.contains(pos)) {
|
|
found = true;
|
|
--range.end;
|
|
continue;
|
|
}
|
|
|
|
if (found) {
|
|
--range.start;
|
|
--range.end;
|
|
}
|
|
}
|
|
check();
|
|
merge();
|
|
}
|
|
|
|
|
|
void Changes::del(Change const change, ChangeTable::size_type const pos)
|
|
{
|
|
// this case happens when building from .lyx
|
|
if (table_.empty()) {
|
|
set(change, pos);
|
|
return;
|
|
}
|
|
|
|
ChangeTable::iterator it = table_.begin();
|
|
|
|
for (; it != table_.end(); ++it) {
|
|
Range & range(it->range);
|
|
|
|
if (range.contains(pos)) {
|
|
if (it->change.type != Change::INSERTED) {
|
|
set(change, pos);
|
|
} else {
|
|
erase(pos);
|
|
}
|
|
break;
|
|
} else if (range.loose_contains(pos) && it + 1 == table_.end()) {
|
|
// this case happens when building from .lyx
|
|
set(change, pos);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Changes::add(Change const change, ChangeTable::size_type const pos)
|
|
{
|
|
ChangeTable::iterator it = table_.begin();
|
|
ChangeTable::iterator end = table_.end();
|
|
|
|
bool found = false;
|
|
|
|
for (; it != end; ++it) {
|
|
Range & range(it->range);
|
|
|
|
if (!found && range.loose_contains(pos)) {
|
|
found = true;
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "Found range of "
|
|
<< range.start << "," << range.end << endl;
|
|
}
|
|
++range.end;
|
|
continue;
|
|
}
|
|
|
|
if (found) {
|
|
++range.start;
|
|
++range.end;
|
|
}
|
|
}
|
|
set(change, pos);
|
|
}
|
|
|
|
|
|
Change const Changes::lookupFull(pos_type const pos) const
|
|
{
|
|
if (!table_.size()) {
|
|
if (lyxerr.debugging(Debug::CHANGES))
|
|
lyxerr[Debug::CHANGES] << "Empty, type is " << empty_type_ << endl;
|
|
return Change(empty_type_);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
check();
|
|
BOOST_ASSERT(false && "missing changes for pos");
|
|
return Change(Change::UNCHANGED);
|
|
}
|
|
|
|
|
|
Change::Type Changes::lookup(pos_type const pos) const
|
|
{
|
|
if (!table_.size()) {
|
|
if (lyxerr.debugging(Debug::CHANGES))
|
|
lyxerr[Debug::CHANGES] << "Empty, type is " << empty_type_ << endl;
|
|
return empty_type_;
|
|
}
|
|
|
|
ChangeTable::const_iterator it = table_.begin();
|
|
ChangeTable::const_iterator end = table_.end();
|
|
|
|
for (; it != end; ++it) {
|
|
if (it->range.contains(pos))
|
|
return it->change.type;
|
|
}
|
|
|
|
check();
|
|
BOOST_ASSERT(false && "missing changes for pos");
|
|
return Change::UNCHANGED;
|
|
}
|
|
|
|
|
|
bool Changes::isChange(pos_type const start, pos_type const end) const
|
|
{
|
|
if (!table_.size()) {
|
|
if (lyxerr.debugging(Debug::CHANGES))
|
|
lyxerr[Debug::CHANGES] << "Empty, type is " << empty_type_ << endl;
|
|
return empty_type_ != Change::UNCHANGED;
|
|
}
|
|
|
|
ChangeTable::const_iterator it = table_.begin();
|
|
ChangeTable::const_iterator const itend = table_.end();
|
|
|
|
for (; it != itend; ++it) {
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "Looking for " << start << ","
|
|
<< end << " in " << it->range.start << ","
|
|
<< it->range.end << "of type " << it->change.type << endl;
|
|
}
|
|
|
|
if (it->range.intersects(Range(start, end))
|
|
&& it->change.type != Change::UNCHANGED) {
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "Found intersection of "
|
|
<< start << "," << end << " with "
|
|
<< it->range.start << "," << it->range.end
|
|
<< " of type " << it->change.type << endl;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Changes::isChangeEdited(lyx::pos_type const start,
|
|
lyx::pos_type const end) const
|
|
{
|
|
if (!table_.size()) {
|
|
if (lyxerr.debugging(Debug::CHANGES))
|
|
lyxerr[Debug::CHANGES] << "Empty, type is " << empty_type_ << endl;
|
|
return empty_type_ != Change::INSERTED;
|
|
}
|
|
|
|
ChangeTable::const_iterator it = table_.begin();
|
|
ChangeTable::const_iterator const itend = table_.end();
|
|
|
|
for (; it != itend; ++it) {
|
|
if (it->range.intersects(Range(start, end ? end - 1 : 0))
|
|
&& it->change.type != Change::INSERTED) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void Changes::merge()
|
|
{
|
|
if (lyxerr.debugging(Debug::CHANGES))
|
|
lyxerr[Debug::CHANGES] << "Starting merge" << endl;
|
|
|
|
ChangeTable::iterator it = table_.begin();
|
|
|
|
while (it != table_.end()) {
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "Range of type " << it->change.type << " is "
|
|
<< it->range.start << "," << it->range.end << endl;
|
|
}
|
|
|
|
if (it->range.start == it->range.end) {
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
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 == (it + 1)->change) {
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "Merging equal ranges "
|
|
<< it->range.start << "," << it->range.end
|
|
<< " and " << (it + 1)->range.start << ","
|
|
<< (it + 1)->range.end << endl;
|
|
}
|
|
|
|
(it + 1)->range.start = it->range.start;
|
|
table_.erase(it);
|
|
// start again
|
|
it = table_.begin();
|
|
continue;
|
|
}
|
|
|
|
++it;
|
|
}
|
|
|
|
lyxerr[Debug::CHANGES] << "Merge ended" << endl;
|
|
check();
|
|
}
|
|
|
|
|
|
void Changes::check() const
|
|
{
|
|
ChangeTable::const_iterator it = table_.begin();
|
|
ChangeTable::const_iterator end = table_.end();
|
|
|
|
bool dont_assert = true;
|
|
|
|
lyxerr[Debug::CHANGES] << "Changelist:" << endl;
|
|
for (; it != end; ++it) {
|
|
if (lyxerr.debugging(Debug::CHANGES)) {
|
|
lyxerr[Debug::CHANGES] << "Range of type " << it->change.type << " is "
|
|
<< it->range.start << "," << it->range.end << " author "
|
|
<< it->change.author << " time " << it->change.changetime << endl;
|
|
}
|
|
|
|
if (it + 1 == end)
|
|
break;
|
|
|
|
Range const & range(it->range);
|
|
Range const & next((it + 1)->range);
|
|
if (range.end != next.start)
|
|
dont_assert = false;
|
|
}
|
|
|
|
if (lyxerr.debugging(Debug::CHANGES))
|
|
lyxerr[Debug::CHANGES] << "End" << endl;
|
|
|
|
BOOST_ASSERT(dont_assert);
|
|
}
|
|
|
|
|
|
int Changes::latexMarkChange(std::ostream & os,
|
|
Change::Type const old, Change::Type const change,
|
|
bool const & output)
|
|
{
|
|
if (!output || old == change)
|
|
return 0;
|
|
|
|
string const start("\\changestart{}");
|
|
string const end("\\changeend{}");
|
|
string const son("\\overstrikeon{}");
|
|
string const soff("\\overstrikeoff{}");
|
|
|
|
int column = 0;
|
|
|
|
if (old == Change::DELETED) {
|
|
os << soff;
|
|
column += soff.length();
|
|
}
|
|
|
|
switch (change) {
|
|
case Change::UNCHANGED:
|
|
os << end;
|
|
column += end.length();
|
|
break;
|
|
|
|
case Change::DELETED:
|
|
if (old == Change::UNCHANGED) {
|
|
os << start;
|
|
column += start.length();
|
|
}
|
|
os << son;
|
|
column += son.length();
|
|
break;
|
|
|
|
case Change::INSERTED:
|
|
if (old == Change::UNCHANGED) {
|
|
os << start;
|
|
column += start.length();
|
|
}
|
|
break;
|
|
}
|
|
|
|
return column;
|
|
}
|
|
|
|
|
|
void Changes::lyxMarkChange(std::ostream & os, int & column,
|
|
lyx::time_type const curtime,
|
|
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: {
|
|
lyx::time_type t = change.changetime;
|
|
if (!t)
|
|
t = curtime;
|
|
os << "\n\\change_deleted " << change.author
|
|
<< " " << t << "\n";
|
|
|
|
break;
|
|
}
|
|
|
|
case Change::INSERTED: {
|
|
lyx::time_type t = change.changetime;
|
|
if (!t)
|
|
t = curtime;
|
|
os << "\n\\change_inserted " << change.author
|
|
<< " " << t << "\n";
|
|
break;
|
|
}
|
|
}
|
|
}
|