lyx_mirror/src/buffer_funcs.C

617 lines
14 KiB
C++
Raw Normal View History

/**
* \file buffer_funcs.C
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Lars Gullik Bj<EFBFBD>nnes
* \author Alfredo Braunstein
*
* Full author contact details are available in file CREDITS.
*
*/
#include <config.h>
#include "buffer_funcs.h"
#include "buffer.h"
#include "bufferlist.h"
#include "bufferparams.h"
#include "dociterator.h"
#include "counters.h"
#include "errorlist.h"
#include "Floating.h"
#include "FloatList.h"
#include "gettext.h"
#include "language.h"
#include "LaTeX.h"
#include "lyxtextclass.h"
#include "paragraph.h"
#include "paragraph_funcs.h"
#include "ParagraphList.h"
#include "ParagraphParameters.h"
#include "pariterator.h"
#include "lyxvc.h"
#include "texrow.h"
#include "vc-backend.h"
#include "toc.h"
#include "frontends/Alert.h"
#include "insets/insetbibitem.h"
#include "support/filetools.h"
#include "support/fs_extras.h"
#include "support/lyxlib.h"
#include <iostream>
#include <boost/bind.hpp>
#include <boost/filesystem/operations.hpp>
using namespace std;
using lyx::pit_type;
using lyx::support::bformat;
using lyx::support::libFileSearch;
using lyx::support::makeDisplayPath;
using lyx::support::onlyFilename;
using lyx::support::onlyPath;
using lyx::support::unlink;
using std::min;
using std::string;
namespace fs = boost::filesystem;
extern BufferList bufferlist;
namespace {
bool readFile(Buffer * const b, string const & s)
{
BOOST_ASSERT(b);
// File information about normal file
if (!fs::exists(s)) {
string const file = makeDisplayPath(s, 50);
string text = bformat(_("The specified document\n%1$s"
"\ncould not be read."), file);
Alert::error(_("Could not read document"), text);
return false;
}
// Check if emergency save file exists and is newer.
string const e = onlyPath(s) + onlyFilename(s) + ".emergency";
if (fs::exists(e) && fs::exists(s)
&& fs::last_write_time(e) > fs::last_write_time(s))
{
string const file = makeDisplayPath(s, 20);
string const text =
bformat(_("An emergency save of the document "
"%1$s exists.\n\n"
"Recover emergency save?"), file);
switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
_("&Recover"), _("&Load Original"),
_("&Cancel")))
{
case 0:
// the file is not saved if we load the emergency file.
b->markDirty();
return b->readFile(e);
case 1:
break;
default:
return false;
}
}
// Now check if autosave file is newer.
string const a = onlyPath(s) + '#' + onlyFilename(s) + '#';
if (fs::exists(a) && fs::exists(s)
&& fs::last_write_time(a) > fs::last_write_time(s))
{
string const file = makeDisplayPath(s, 20);
string const text =
bformat(_("The backup of the document "
"%1$s is newer.\n\nLoad the "
"backup instead?"), file);
switch (Alert::prompt(_("Load backup?"), text, 0, 2,
_("&Load backup"), _("Load &original"),
_("&Cancel") ))
{
case 0:
// the file is not saved if we load the autosave file.
b->markDirty();
return b->readFile(a);
case 1:
// Here we delete the autosave
unlink(a);
break;
default:
return false;
}
}
return b->readFile(s);
}
} // namespace anon
bool loadLyXFile(Buffer * b, string const & s)
{
BOOST_ASSERT(b);
if (fs::is_readable(s)) {
if (readFile(b, s)) {
b->lyxvc().file_found_hook(s);
if (!fs::is_writable(s))
b->setReadonly(true);
return true;
}
} else {
string const file = makeDisplayPath(s, 20);
// Here we probably should run
if (LyXVC::file_not_found_hook(s)) {
string const text =
bformat(_("Do you want to retrieve the document"
" %1$s from version control?"), file);
int const ret = Alert::prompt(_("Retrieve from version control?"),
text, 0, 1, _("&Retrieve"), _("&Cancel"));
if (ret == 0) {
// How can we know _how_ to do the checkout?
// With the current VC support it has to be,
// a RCS file since CVS do not have special ,v files.
RCS::retrieve(s);
return loadLyXFile(b, s);
}
}
}
return false;
}
Buffer * newFile(string const & filename, string const & templatename,
bool const isNamed)
{
// get a free buffer
Buffer * b = bufferlist.newBuffer(filename);
BOOST_ASSERT(b);
string tname;
// use defaults.lyx as a default template if it exists.
if (templatename.empty())
tname = libFileSearch("templates", "defaults.lyx");
else
tname = templatename;
if (!tname.empty()) {
if (!b->readFile(tname)) {
string const file = makeDisplayPath(tname, 50);
string const text = bformat(_("The specified document template\n%1$s\ncould not be read."), file);
Alert::error(_("Could not read template"), text);
bufferlist.release(b);
return 0;
}
}
if (!isNamed) {
b->setUnnamed();
b->setFileName(filename);
}
b->setReadonly(false);
b->fully_loaded(true);
b->updateDocLang(b->params().language);
return b;
}
void bufferErrors(Buffer const & buf, TeXErrors const & terr)
{
TeXErrors::Errors::const_iterator cit = terr.begin();
TeXErrors::Errors::const_iterator end = terr.end();
for (; cit != end; ++cit) {
int id_start = -1;
int pos_start = -1;
int errorrow = cit->error_in_line;
bool found = buf.texrow().getIdFromRow(errorrow, id_start,
pos_start);
int id_end = -1;
int pos_end = -1;
do {
++errorrow;
found = buf.texrow().getIdFromRow(errorrow, id_end,
pos_end);
} while (found && id_start == id_end && pos_start == pos_end);
This commit transfer the ErrorList handling from LyXView to Buffer. It also removes the need for the error signal and simplify the kerbel <-> frontend communication. I think it should speed-up _significantly_ the latex compilation for example in case of problematic files. TODO 1: All occurrences of "LyXView::showErrorList()" in the "kernel" should be replaced by a boost signal emission (Buffer::errors()). This signal is already connected to this showErrorList() slot. TODO 2: The ErrorList mechanism is used wrongly in a number of place, most notably in "Converter.C". Instead of replacing the ErrorList in the "Buffer" class, the "Converter" class should maintain its own list instead and connect directly to the LyXView::showErrorList() slot. Buffer: * errorList_: new private member and associated access methods. * setErrorList(): new accessor method. * addError(): apend an error to the errorList_. * error(): deleted. * errors(): new boost signal, unused for now. Shall be used instead of LyXView::showErrorList(). LyXView: * getErrorList(), addError(), errorlist_, errorConnection_: deleted. * errorsConnection_: new boost connection for the Buffer::errors() signal. lyx_main.C: * LyX::exec2(): manually print all errors. BufferView.h: remove unneeded ErrorList forward declaration. BufferView::pimpl::menuInsertLyXFile(): delete Buffer::error() connection and add a FIXME comment text.C: Use Buffer::addError() instead of Buffer::error() signal emission. ControlErrorList.C: get the ErrorList from the Buffer instead of LyXView git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@14467 a592a061-630c-0410-9148-cb99ea01b6c8
2006-07-15 22:43:37 +00:00
buf.addError(ErrorItem(cit->error_desc,
cit->error_text, id_start, pos_start, pos_end));
}
}
void bufferErrors(Buffer const & buf, ErrorList const & el)
{
This commit transfer the ErrorList handling from LyXView to Buffer. It also removes the need for the error signal and simplify the kerbel <-> frontend communication. I think it should speed-up _significantly_ the latex compilation for example in case of problematic files. TODO 1: All occurrences of "LyXView::showErrorList()" in the "kernel" should be replaced by a boost signal emission (Buffer::errors()). This signal is already connected to this showErrorList() slot. TODO 2: The ErrorList mechanism is used wrongly in a number of place, most notably in "Converter.C". Instead of replacing the ErrorList in the "Buffer" class, the "Converter" class should maintain its own list instead and connect directly to the LyXView::showErrorList() slot. Buffer: * errorList_: new private member and associated access methods. * setErrorList(): new accessor method. * addError(): apend an error to the errorList_. * error(): deleted. * errors(): new boost signal, unused for now. Shall be used instead of LyXView::showErrorList(). LyXView: * getErrorList(), addError(), errorlist_, errorConnection_: deleted. * errorsConnection_: new boost connection for the Buffer::errors() signal. lyx_main.C: * LyX::exec2(): manually print all errors. BufferView.h: remove unneeded ErrorList forward declaration. BufferView::pimpl::menuInsertLyXFile(): delete Buffer::error() connection and add a FIXME comment text.C: Use Buffer::addError() instead of Buffer::error() signal emission. ControlErrorList.C: get the ErrorList from the Buffer instead of LyXView git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@14467 a592a061-630c-0410-9148-cb99ea01b6c8
2006-07-15 22:43:37 +00:00
buf.setErrorList(el);
}
string const bufferFormat(Buffer const & buffer)
{
if (buffer.isLinuxDoc())
return "linuxdoc";
else if (buffer.isDocBook())
return "docbook";
else if (buffer.isLiterate())
return "literate";
else
return "latex";
}
int countWords(DocIterator const & from, DocIterator const & to)
{
int count = 0;
bool inword = false;
for (DocIterator dit = from ; dit != to ; dit.forwardPos()) {
// Copied and adapted from isLetter() in ControlSpellChecker
if (dit.inTexted()
&& dit.pos() != dit.lastpos()
&& dit.paragraph().isLetter(dit.pos())
&& !isDeletedText(dit.paragraph(), dit.pos())) {
if (!inword) {
++count;
inword = true;
}
} else if (inword)
inword = false;
}
return count;
}
namespace {
lyx::depth_type getDepth(DocIterator const & it)
{
lyx::depth_type depth = 0;
for (size_t i = 0 ; i < it.depth() ; ++i)
if (!it[i].inset().inMathed())
depth += it[i].paragraph().getDepth() + 1;
// remove 1 since the outer inset does not count
return depth - 1;
}
lyx::depth_type getItemDepth(ParIterator const & it)
{
Paragraph const & par = *it;
LYX_LABEL_TYPES const labeltype = par.layout()->labeltype;
if (labeltype != LABEL_ENUMERATE && labeltype != LABEL_ITEMIZE)
return 0;
// this will hold the lowest depth encountered up to now.
lyx::depth_type min_depth = getDepth(it);
ParIterator prev_it = it;
while (true) {
if (prev_it.pit())
--prev_it.top().pit();
else {
// start of nested inset: go to outer par
prev_it.pop_back();
if (prev_it.empty()) {
// start of document: nothing to do
return 0;
}
}
// We search for the first paragraph with same label
// that is not more deeply nested.
Paragraph & prev_par = *prev_it;
lyx::depth_type const prev_depth = getDepth(prev_it);
if (labeltype == prev_par.layout()->labeltype) {
if (prev_depth < min_depth) {
return prev_par.itemdepth + 1;
}
else if (prev_depth == min_depth) {
return prev_par.itemdepth;
}
}
min_depth = std::min(min_depth, prev_depth);
// small optimization: if we are at depth 0, we won't
// find anything else
if (prev_depth == 0) {
return 0;
}
}
}
bool needEnumCounterReset(ParIterator const & it)
{
Paragraph const & par = *it;
BOOST_ASSERT(par.layout()->labeltype == LABEL_ENUMERATE);
lyx::depth_type const cur_depth = par.getDepth();
ParIterator prev_it = it;
while (prev_it.pit()) {
--prev_it.top().pit();
Paragraph const & prev_par = *prev_it;
if (prev_par.getDepth() <= cur_depth)
return prev_par.layout()->labeltype != LABEL_ENUMERATE;
}
// start of nested inset: reset
return true;
}
// set the label of a paragraph. This includes the counters.
void setLabel(Buffer const & buf, ParIterator & it)
{
Paragraph & par = *it;
BufferParams const & bufparams = buf.params();
LyXTextClass const & textclass = bufparams.getLyXTextClass();
LyXLayout_ptr const & layout = par.layout();
Counters & counters = textclass.counters();
if (it.pit() == 0) {
par.params().appendix(par.params().startOfAppendix());
} else {
par.params().appendix(it.plist()[it.pit() - 1].params().appendix());
if (!par.params().appendix() &&
par.params().startOfAppendix()) {
par.params().appendix(true);
textclass.counters().reset();
}
}
// Compute the item depth of the paragraph
par.itemdepth = getItemDepth(it);
// erase what was there before
par.params().labelString(string());
if (layout->margintype == MARGIN_MANUAL) {
if (par.params().labelWidthString().empty())
par.setLabelWidthString(layout->labelstring());
} else {
par.setLabelWidthString(string());
}
// is it a layout that has an automatic label?
if (layout->labeltype == LABEL_COUNTER) {
if (layout->toclevel <= buf.params().secnumdepth
&& (layout->latextype != LATEX_ENVIRONMENT
|| isFirstInSequence(it.pit(), it.plist()))) {
counters.step(layout->counter);
string label = expandLabel(buf, layout,
par.params().appendix());
par.params().labelString(label);
}
} else if (layout->labeltype == LABEL_ITEMIZE) {
// At some point of time we should do something more
// clever here, like:
// par.params().labelString(
// bufparams.user_defined_bullet(par.itemdepth).getText());
// for now, use a simple hardcoded label
string itemlabel;
switch (par.itemdepth) {
case 0:
itemlabel = "*";
break;
case 1:
itemlabel = "-";
break;
case 2:
itemlabel = "@";
break;
case 3:
itemlabel = "<EFBFBD>";
break;
}
par.params().labelString(itemlabel);
} else if (layout->labeltype == LABEL_ENUMERATE) {
// FIXME
// Yes I know this is a really, really! bad solution
// (Lgb)
string enumcounter = "enum";
switch (par.itemdepth) {
case 2:
enumcounter += 'i';
case 1:
enumcounter += 'i';
case 0:
enumcounter += 'i';
break;
case 3:
enumcounter += "iv";
break;
default:
// not a valid enumdepth...
break;
}
// Maybe we have to reset the enumeration counter.
if (needEnumCounterReset(it))
counters.reset(enumcounter);
counters.step(enumcounter);
string format;
switch (par.itemdepth) {
case 0:
format = N_("\\arabic{enumi}.");
break;
case 1:
format = N_("(\\alph{enumii})");
break;
case 2:
format = N_("\\roman{enumiii}.");
break;
case 3:
format = N_("\\Alph{enumiv}.");
break;
default:
// not a valid enumdepth...
break;
}
par.params().labelString(counters.counterLabel(buf.B_(format)));
} else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
counters.step("bibitem");
int number = counters.value("bibitem");
if (par.bibitem())
par.bibitem()->setCounter(number);
par.params().labelString(buf.B_(layout->labelstring()));
// In biblio should't be following counters but...
} else if (layout->labeltype == LABEL_SENSITIVE) {
// Search for the first float or wrap inset in the iterator
string type;
size_t i = it.depth();
while (i > 0) {
--i;
InsetBase * const in = &it[i].inset();
if (in->lyxCode() == InsetBase::FLOAT_CODE
|| in->lyxCode() == InsetBase::WRAP_CODE) {
type = in->getInsetName();
break;
}
}
string s;
if (!type.empty()) {
Floating const & fl = textclass.floats().getType(type);
counters.step(fl.type());
// Doesn't work... yet.
s = bformat(_("%1$s #:"), buf.B_(fl.name()));
} else {
// par->SetLayout(0);
s = buf.B_(layout->labelstring());
}
par.params().labelString(s);
} else if (layout->labeltype == LABEL_NO_LABEL)
par.params().labelString(string());
else
par.params().labelString(buf.B_(layout->labelstring()));
}
} // anon namespace
bool updateCurrentLabel(Buffer const & buf,
ParIterator & it)
{
if (it == par_iterator_end(buf.inset()))
return false;
// if (it.lastpit == 0 && LyXText::isMainText())
// return false;
switch (it->layout()->labeltype) {
case LABEL_NO_LABEL:
case LABEL_MANUAL:
case LABEL_BIBLIO:
case LABEL_TOP_ENVIRONMENT:
case LABEL_CENTERED_TOP_ENVIRONMENT:
case LABEL_STATIC:
case LABEL_ITEMIZE:
setLabel(buf, it);
return true;
case LABEL_SENSITIVE:
case LABEL_COUNTER:
// do more things with enumerate later
case LABEL_ENUMERATE:
return false;
}
// This is dead code which get rid of a warning:
return true;
}
void updateLabels(Buffer const & buf,
ParIterator & from, ParIterator & to)
{
for (ParIterator it = from; it != to; ++it) {
if (it.pit() > it.lastpit())
return;
if (!updateCurrentLabel (buf, it)) {
updateLabels(buf);
return;
}
}
}
void updateLabels(Buffer const & buf,
ParIterator & iter)
{
if (updateCurrentLabel(buf, iter))
return;
updateLabels(buf);
}
void updateLabels(Buffer const & buf)
{
// start over the counters
buf.params().getLyXTextClass().counters().reset();
ParIterator const end = par_iterator_end(buf.inset());
for (ParIterator it = par_iterator_begin(buf.inset()); it != end; ++it) {
// reduce depth if necessary
if (it.pit()) {
Paragraph const & prevpar = it.plist()[it.pit() - 1];
it->params().depth(min(it->params().depth(),
prevpar.getMaxDepthAfter()));
} else
it->params().depth(0);
// set the counter for this paragraph
setLabel(buf, it);
}
lyx::toc::updateToc(buf);
}
string expandLabel(Buffer const & buf,
LyXLayout_ptr const & layout, bool appendix)
{
LyXTextClass const & tclass = buf.params().getLyXTextClass();
string fmt = buf.B_(appendix ? layout->labelstring_appendix()
: layout->labelstring());
// handle 'inherited level parts' in 'fmt',
// i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
size_t const i = fmt.find('@', 0);
if (i != string::npos) {
size_t const j = fmt.find('@', i + 1);
if (j != string::npos) {
string parent(fmt, i + 1, j - i - 1);
string label = expandLabel(buf, tclass[parent], appendix);
fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
}
}
return tclass.counters().counterLabel(fmt);
}