Improvements to the display of the source.

Fix bugs #6501 and #7359.

* Selection highlighting has been broken since a conflict with
  reverse-search was repaired (at 00a62b7c), is now fixed.

* The selection highlighting was not accurate, for LaTeX formats (in
  full source view), and meaningless, for non LaTeX formats.

* fix regression at cc00b9aa: force_getcontent_ was always true
  therefore the code to detect changes in the generated source was
  dead. The consequence is that the source view would jump to the
  beginning at each updateView() even if no change occurred. Cc00b9aa
  was meant to fix #5600, which I cannot reproduce with the new
  implementation.

* Various improvements:
  *  When the position-to-line conversion is unavailable (LyXHTML, LyX
     source, etc.) we focus on the first difference instead.
  *  Get some space around the cursor
  *  Respect the scrollbars
  *  Highlight with QTextEdit::ExtraSelections instead of cursor
     selection (the latter used to break syntax highlighting of the
     TeX code... which was not so much of an issue before because the
     wrong part was selected)

Known issues:
  * The highlighting is off by one line in very last paragraph of a
    document. This appears to be a bug in TexRow.cpp.
  * The highlighting is off for any kind of inset. This could be
    solved by adapting TexRow so that it accepts CursorSlices as
    and argument for the conversion to line number. (this is bug
    #4725)
This commit is contained in:
Guillaume Munch 2015-08-04 23:16:40 +01:00
parent 120c99ae60
commit fd7b13f5ae
4 changed files with 172 additions and 71 deletions

View File

@ -3510,11 +3510,12 @@ void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to)
} }
} }
// returns NULL if id-to-row conversion is unsupported
void Buffer::getSourceCode(odocstream & os, string const & format, auto_ptr<TexRow> Buffer::getSourceCode(odocstream & os, string const & format,
pit_type par_begin, pit_type par_end, pit_type par_begin, pit_type par_end,
OutputWhat output, bool master) const OutputWhat output, bool master) const
{ {
auto_ptr<TexRow> texrow(NULL);
OutputParams runparams(&params().encoding()); OutputParams runparams(&params().encoding());
runparams.nice = true; runparams.nice = true;
runparams.flavor = params().getOutputFlavor(format); runparams.flavor = params().getOutputFlavor(format);
@ -3568,12 +3569,12 @@ void Buffer::getSourceCode(odocstream & os, string const & format,
LaTeXFeatures features(*this, params(), runparams); LaTeXFeatures features(*this, params(), runparams);
params().validate(features); params().validate(features);
runparams.use_polyglossia = features.usePolyglossia(); runparams.use_polyglossia = features.usePolyglossia();
TexRow texrow; texrow.reset(new TexRow());
texrow.reset(); texrow->reset();
texrow.newline(); texrow->newline();
texrow.newline(); texrow->newline();
// latex or literate // latex or literate
otexstream ots(os, texrow); otexstream ots(os, *texrow);
// the real stuff // the real stuff
latexParagraphs(*this, text(), ots, runparams); latexParagraphs(*this, text(), ots, runparams);
@ -3611,15 +3612,17 @@ void Buffer::getSourceCode(odocstream & os, string const & format,
writeDocBookSource(os, absFileName(), runparams, output); writeDocBookSource(os, absFileName(), runparams, output);
} else { } else {
// latex or literate // latex or literate
d->texrow.reset(); texrow.reset(new TexRow());
d->texrow.newline(); texrow->reset();
d->texrow.newline(); texrow->newline();
otexstream ots(os, d->texrow); texrow->newline();
otexstream ots(os, *texrow);
if (master) if (master)
runparams.is_child = true; runparams.is_child = true;
writeLaTeXSource(ots, string(), runparams, output); writeLaTeXSource(ots, string(), runparams, output);
} }
} }
return texrow;
} }

View File

@ -618,9 +618,10 @@ public:
/// get source code (latex/docbook) for some paragraphs, or all paragraphs /// get source code (latex/docbook) for some paragraphs, or all paragraphs
/// including preamble /// including preamble
void getSourceCode(odocstream & os, std::string const & format, /// returns NULL if Id to Row conversion is unsupported
pit_type par_begin, pit_type par_end, OutputWhat output, std::auto_ptr<TexRow> getSourceCode(odocstream & os,
bool master) const; std::string const & format, pit_type par_begin,
pit_type par_end, OutputWhat output, bool master) const;
/// Access to error list. /// Access to error list.
/// This method is used only for GUI visualisation of Buffer related /// This method is used only for GUI visualisation of Buffer related

View File

@ -17,13 +17,11 @@
#include "LaTeXHighlighter.h" #include "LaTeXHighlighter.h"
#include "qt_helpers.h" #include "qt_helpers.h"
#include "Buffer.h"
#include "BufferParams.h" #include "BufferParams.h"
#include "BufferView.h" #include "BufferView.h"
#include "Cursor.h" #include "Cursor.h"
#include "Format.h" #include "Format.h"
#include "Paragraph.h" #include "Paragraph.h"
#include "TexRow.h"
#include "support/debug.h" #include "support/debug.h"
#include "support/lassert.h" #include "support/lassert.h"
@ -34,6 +32,7 @@
#include <QBoxLayout> #include <QBoxLayout>
#include <QComboBox> #include <QComboBox>
#include <QScrollBar>
#include <QSettings> #include <QSettings>
#include <QTextCursor> #include <QTextCursor>
#include <QTextDocument> #include <QTextDocument>
@ -47,7 +46,6 @@ namespace frontend {
ViewSourceWidget::ViewSourceWidget() ViewSourceWidget::ViewSourceWidget()
: bv_(0), document_(new QTextDocument(this)), : bv_(0), document_(new QTextDocument(this)),
highlighter_(new LaTeXHighlighter(document_)), highlighter_(new LaTeXHighlighter(document_)),
force_getcontent_(true),
update_timer_(new QTimer(this)) update_timer_(new QTimer(this))
{ {
setupUi(this); setupUi(this);
@ -76,6 +74,8 @@ ViewSourceWidget::ViewSourceWidget()
// so we disable the signals here: // so we disable the signals here:
document_->blockSignals(true); document_->blockSignals(true);
viewSourceTV->setDocument(document_); viewSourceTV->setDocument(document_);
// reset selections
setText();
document_->blockSignals(false); document_->blockSignals(false);
viewSourceTV->setReadOnly(true); viewSourceTV->setReadOnly(true);
///dialog_->viewSourceTV->setAcceptRichText(false); ///dialog_->viewSourceTV->setAcceptRichText(false);
@ -89,21 +89,9 @@ ViewSourceWidget::ViewSourceWidget()
} }
static size_t crcCheck(docstring const & s) auto_ptr<TexRow> ViewSourceWidget::getContent(BufferView const * view,
{ Buffer::OutputWhat output, docstring & str, string const & format,
boost::crc_32_type crc; bool master)
crc.process_bytes(&s[0], sizeof(char_type) * s.size());
return crc.checksum();
}
/** get the source code of selected paragraphs, or the whole document
\param fullSource get full source code
\return true if the content has changed since last call.
*/
static bool getContent(BufferView const * view, Buffer::OutputWhat output,
QString & qstr, string const & format, bool force_getcontent,
bool master)
{ {
// get the *top* level paragraphs that contain the cursor, // get the *top* level paragraphs that contain the cursor,
// or the selected text // or the selected text
@ -120,31 +108,33 @@ static bool getContent(BufferView const * view, Buffer::OutputWhat output,
if (par_begin > par_end) if (par_begin > par_end)
swap(par_begin, par_end); swap(par_begin, par_end);
odocstringstream ostr; odocstringstream ostr;
view->buffer().getSourceCode(ostr, format, par_begin, par_end + 1, auto_ptr<TexRow> texrow = view->buffer().getSourceCode(ostr, format,
output, master); par_begin, par_end + 1, output, master);
docstring s = ostr.str(); str = ostr.str();
// FIXME THREAD return texrow;
// Could this be private to this particular dialog? We could have
// more than one of these, in different windows.
static size_t crc = 0;
size_t newcrc = crcCheck(s);
if (newcrc == crc && !force_getcontent)
return false;
crc = newcrc;
qstr = toqstr(s);
return true;
} }
void ViewSourceWidget::setBufferView(BufferView const * bv) void ViewSourceWidget::setBufferView(BufferView const * bv)
{ {
if (bv_ != bv) if (bv_ != bv) {
force_getcontent_ = true; setText();
bv_ = bv; bv_ = bv;
}
setEnabled(bv ? true : false); setEnabled(bv ? true : false);
} }
bool ViewSourceWidget::setText(QString const & qstr)
{
bool const changed = document_->toPlainText() != qstr;
viewSourceTV->setExtraSelections(QList<QTextEdit::ExtraSelection>());
if (changed)
document_->setPlainText(qstr);
return changed;
}
void ViewSourceWidget::contentsChanged() void ViewSourceWidget::contentsChanged()
{ {
if (autoUpdateCB->isChecked()) if (autoUpdateCB->isChecked())
@ -176,16 +166,21 @@ void ViewSourceWidget::updateViewNow()
void ViewSourceWidget::realUpdateView() void ViewSourceWidget::realUpdateView()
{ {
if (!bv_) { if (!bv_) {
document_->setPlainText(QString()); setText();
setEnabled(false); setEnabled(false);
return; return;
} }
setEnabled(true); setEnabled(true);
// we will try to get that much space around the cursor
int const v_margin = 3;
int const h_margin = 10;
// we will try to preserve this
int const h_scroll = viewSourceTV->horizontalScrollBar()->value();
string const format = fromqstr(view_format_); string const format = fromqstr(view_format_);
QString content;
Buffer::OutputWhat output = Buffer::CurrentParagraph; Buffer::OutputWhat output = Buffer::CurrentParagraph;
if (contentsCO->currentIndex() == 1) if (contentsCO->currentIndex() == 1)
output = Buffer::FullSource; output = Buffer::FullSource;
@ -194,27 +189,121 @@ void ViewSourceWidget::realUpdateView()
else if (contentsCO->currentIndex() == 3) else if (contentsCO->currentIndex() == 3)
output = Buffer::OnlyBody; output = Buffer::OnlyBody;
if (getContent(bv_, output, content, format, docstring content;
force_getcontent_, masterPerspectiveCB->isChecked())) auto_ptr<TexRow> texrow = getContent(bv_, output, content, format,
document_->setPlainText(content); masterPerspectiveCB->isChecked());
QString old = document_->toPlainText();
QString qcontent = toqstr(content);
bool const changed = setText(qcontent);
CursorSlice beg = bv_->cursor().selectionBegin().bottom(); if (changed && !texrow.get()) {
CursorSlice end = bv_->cursor().selectionEnd().bottom(); // position-to-row is unavailable
int const begrow = bv_->buffer().texrow(). // we jump to the first modification
getRowFromIdPos(beg.paragraph().id(), beg.pos()); const QChar * oc = old.constData();
int endrow = bv_->buffer().texrow(). const QChar * nc = qcontent.constData();
getRowFromIdPos(end.paragraph().id(), end.pos()); int pos = 0;
int const nextendrow = bv_->buffer().texrow(). while (*oc != '\0' && *nc != '\0' && *oc == *nc) {
getRowFromIdPos(end.paragraph().id(), end.pos() + 1); ++oc;
if (endrow != nextendrow) ++nc;
endrow = nextendrow - 1; ++pos;
}
QTextCursor c = QTextCursor(viewSourceTV->document());
//get some space below the cursor
c.setPosition(pos);
c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,v_margin);
viewSourceTV->setTextCursor(c);
//get some space on the right of the cursor
viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
c.setPosition(pos);
const int block = c.blockNumber();
for (int i = h_margin; i && block == c.blockNumber(); --i) {
c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
}
c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
viewSourceTV->setTextCursor(c);
//back to the position
c.setPosition(pos);
//c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
viewSourceTV->setTextCursor(c);
QTextCursor c = QTextCursor(viewSourceTV->document()); } else if (texrow.get()) {
c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, begrow); // Use the available position-to-row conversion to highlight
c.select(QTextCursor::BlockUnderCursor); // the current selection in the source
c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, //
endrow - begrow + 1); // FIXME:
viewSourceTV->setTextCursor(c); // * it is currently impossible to highlight the very last line
// of a document, because TexRow gives the wrong data.
// * we currently only compute the top-level position, which
// makes it impossible to highlight inside an inset. It is not
// a limitation of TexRow, but replacing bottom() with top()
// works partially and causes segfaults with math. Solving
// this could be seen as a solution to #4725.
// * even if we keep computing the top-level position, the data
// given by TexRow is false if there is e.g. a float of a
// footnote in the paragraph
CursorSlice beg = bv_->cursor().selectionBegin().bottom();
CursorSlice end = bv_->cursor().selectionEnd().bottom();
int const beg_par = beg.paragraph().id();
int const end_par = end.paragraph().id();
int const beg_pos = beg.pos();
int const end_pos = end.pos();
int const beg_row = texrow->getRowFromIdPos(beg_par, beg_pos);
int end_row, next_end_row;
if (beg_par != end_par || beg_pos != end_pos) {
end_row = texrow->getRowFromIdPos(end_par, max(0, end_pos - 1));
next_end_row = texrow->getRowFromIdPos(end_par, end_pos);
} else {
end_row = beg_row;
next_end_row = texrow->getRowFromIdPos(beg_par, beg_pos + 1);
}
if (end_row != next_end_row)
end_row = next_end_row - 1;
QTextCursor c = QTextCursor(viewSourceTV->document());
c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
beg_row - 1);
const int beg_sel = c.position();
//get some space above the cursor
c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
v_margin);
viewSourceTV->setTextCursor(c);
c.setPosition(beg_sel, QTextCursor::MoveAnchor);
c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
end_row - beg_row +1);
const int end_sel = c.position();
//get some space below the cursor
c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
v_margin - 1);
viewSourceTV->setTextCursor(c);
c.setPosition(end_sel, QTextCursor::KeepAnchor);
viewSourceTV->setTextCursor(c);
//the real highlighting is done with an ExtraSelection
QTextCharFormat format;
QPalette palette = viewSourceTV->palette();
//Alternative:
// QColor bg = palette.color(QPalette::Active,QPalette::Highlight);
// bg.setAlpha(64);
// format.setBackground(QBrush(bg));
//Other alternatives:
//format.setBackground(palette.light());
//format.setBackground(palette.alternateBase());
format.setBackground(palette.toolTipBase());
format.setProperty(QTextFormat::FullWidthSelection, true);
QTextEdit::ExtraSelection sel;
sel.format = format;
sel.cursor = c;
viewSourceTV->setExtraSelections(
QList<QTextEdit::ExtraSelection>() << sel);
//clean up
c.clearSelection();
viewSourceTV->setTextCursor(c);
viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
}
} }
@ -340,6 +429,7 @@ QString GuiViewSource::title() const
{ {
switch (docType()) { switch (docType()) {
case LATEX: case LATEX:
//FIXME: this is shown for LyXHTML source, LyX source, etc.
return qt_("LaTeX Source"); return qt_("LaTeX Source");
case DOCBOOK: case DOCBOOK:
return qt_("DocBook Source"); return qt_("DocBook Source");

View File

@ -16,7 +16,9 @@
#include "ui_ViewSourceUi.h" #include "ui_ViewSourceUi.h"
#include "Buffer.h"
#include "DockView.h" #include "DockView.h"
#include "TexRow.h"
#include <QDockWidget> #include <QDockWidget>
#include <QString> #include <QString>
@ -38,6 +40,8 @@ public:
ViewSourceWidget(); ViewSourceWidget();
/// ///
void setBufferView(BufferView const * bv); void setBufferView(BufferView const * bv);
/// returns true if the string has changed
bool setText(QString const & qstr = QString());
/// ///
void saveSession(QString const & session_key) const; void saveSession(QString const & session_key) const;
/// ///
@ -64,6 +68,11 @@ private Q_SLOTS:
void realUpdateView(); void realUpdateView();
private: private:
/// Get the source code of selected paragraphs, or the whole document.
/// If TexRow is unavailable for the format then t is null.
std::auto_ptr<TexRow> getContent(BufferView const * view,
Buffer::OutputWhat output, docstring & str,
std::string const & format, bool master);
/// ///
BufferView const * bv_; BufferView const * bv_;
/// ///
@ -71,8 +80,6 @@ private:
/// LaTeX syntax highlighter /// LaTeX syntax highlighter
LaTeXHighlighter * highlighter_; LaTeXHighlighter * highlighter_;
/// ///
bool force_getcontent_;
///
QString view_format_; QString view_format_;
/// ///
QTimer * update_timer_; QTimer * update_timer_;