Fix bugs #8546 and #9055, and introduce new separator inset.

The algorithm used for breaking a paragraph in LaTeX export is changed
for avoiding spurious blank lines causing too much vertical space.
This change is tied to the introduction of a new inset (with two
different specializations) helping in either outputing LaTeX paragraph
breaks or separating environments in LyX. Both of the above goals were
previously achieved by the ---Separator--- layout and can now be
accomplished by the new inset in a more natural way. As an example,
after leaving an environment by hitting the Return key for two times,
a third return automatically inserts a parbreak inset, which is
equivalent to the old separator layout, i.e., it also introduces a
blank line in the output. If this blank line is not wanted, the
parbreak separator can be changed to a plain separator by a right
click of the mouse. Of course, an environment can still be separated
by the following one by using the Alt+P+Return shortcut (or the
corresponding menu key), but now the plain separator inset is used
instead of the old separator layout, such that no blank line occurs in
the LaTeX output.

Old documents are converted such that the LaTeX output remains unchanged.
As a result of this conversion, the old separator layout is replaced by
the new parbreak inset, which may also appear in places where the old
algorithm was introducing blank lines while the new one is not.
Note that not all blank lines were actually affecting the LaTeX output,
because a blank line is simply ignored by the TeX engine when it occurs
in the so called "vertical mode" (e.g., after an alignment environment).
The old ---Separator--- layout is now gone and old layout files using it
are also automatically converted.

Round trip conversions between old and new format should leave a document
unchanged. This means that the new behavior about paragraph breaking is
not "carried back" to the old format. Indeed, this would need introducing
special LaTeX commands in ERT that would accumulate in roundtrip
conversions, horribly cluttering the document. So, when converting a
modified document to old formats, the LaTeX output may slightly differ in
vertical spacing if the document is processed by an old version of LyX.
In other words, forward compatibility is guaranteed, but not backwards.
This commit is contained in:
Enrico Forestieri 2014-05-10 23:25:11 +02:00
parent 2ebcf38493
commit c668ebf611
23 changed files with 840 additions and 90 deletions

View File

@ -11,6 +11,14 @@ adjustments are made to tex2lyx and bugs are fixed in lyx2lyx.
-----------------------
2014-05-05 Enrico Forestieri <forenr@lyx.org>
* Format incremented to 475
New Separator insets. The parbreak separator introduces a LaTeX
paragraph break in the output. The plain separator does nothing
and its purpose is replacing the Separator layout for separating
environments. The new parbreak separator is roughly equivalent
to the old Separator layout.
2013-05-30 Richard Heck <rgheck@lyx.org>
* Format increments to 474: dummy format for conversion of Chunk layouts
to insets

View File

@ -84,7 +84,7 @@ format_relation = [("0_06", [200], minor_versions("0.6" , 4)),
("1_6", range(277,346), minor_versions("1.6" , 10)),
("2_0", range(346,414), minor_versions("2.0", 8)),
("2_1", range(414,475), minor_versions("2.1", 0)),
("2_2", [], minor_versions("2.2", 0))
("2_2", range(475,476), minor_versions("2.2", 0))
]
####################################################################

View File

@ -34,6 +34,9 @@ import sys, os
# put_cmd_in_ert, lyx2latex, latex_length, revert_flex_inset, \
# revert_font_attrs, hex2ratio, str2bool
from parser_tools import find_token, find_token_backwards, find_re, \
find_end_of_inset, find_end_of_layout, find_nonempty_line, \
get_containing_layout, get_value, check_token
###############################################################################
###
@ -41,16 +44,208 @@ import sys, os
###
###############################################################################
def convert_separator(document):
"""
Convert layout separators to separator insets and add (LaTeX) paragraph
breaks in order to mimic previous LaTeX export.
"""
parins = ["\\begin_inset Separator parbreak", "\\end_inset", ""]
parlay = ["\\begin_layout Standard", "\\begin_inset Separator parbreak",
"\\end_inset", "", "\\end_layout", ""]
sty_dict = {
"family" : "default",
"series" : "default",
"shape" : "default",
"size" : "default",
"bar" : "default",
"color" : "inherit"
}
i = 0
while 1:
i = find_token(document.body, "\\begin_deeper", i)
if i == -1:
break
j = find_token_backwards(document.body, "\\end_layout", i-1)
if j != -1:
# reset any text style before inserting the inset
lay = get_containing_layout(document.body, j-1)
if lay != False:
content = "\n".join(document.body[lay[1]:lay[2]])
for val in sty_dict.keys():
if content.find("\\%s" % val) != -1:
document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
i = i + 1
j = j + 1
document.body[j:j] = parins
i = i + len(parins) + 1
else:
i = i + 1
i = 0
while 1:
i = find_token(document.body, "\\align", i)
if i == -1:
break
lay = get_containing_layout(document.body, i)
if lay != False and lay[0] == "Plain Layout":
i = i + 1
continue
j = find_token_backwards(document.body, "\\end_layout", i-1)
if j != -1:
lay = get_containing_layout(document.body, j-1)
if lay != False and lay[0] == "Standard" \
and find_token(document.body, "\\align", lay[1], lay[2]) == -1 \
and find_token(document.body, "\\begin_inset VSpace", lay[1], lay[2]) == -1:
# reset any text style before inserting the inset
content = "\n".join(document.body[lay[1]:lay[2]])
for val in sty_dict.keys():
if content.find("\\%s" % val) != -1:
document.body[j:j] = ["\\%s %s" % (val, sty_dict[val])]
i = i + 1
j = j + 1
document.body[j:j] = parins
i = i + len(parins) + 1
else:
i = i + 1
else:
i = i + 1
regexp = re.compile(r'^\\begin_layout (?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
i = 0
while 1:
i = find_re(document.body, regexp, i)
if i == -1:
return
j = find_end_of_layout(document.body, i)
if j == -1:
document.warning("Malformed LyX document: Missing `\\end_layout'.")
return
lay = get_containing_layout(document.body, j-1)
if lay != False:
lines = document.body[lay[3]:lay[2]]
else:
lines = []
document.body[i:j+1] = parlay
if len(lines) > 0:
document.body[i+1:i+1] = lines
i = i + len(parlay) + len(lines) + 1
def revert_separator(document):
" Revert separator insets to layout separators "
parsep = ["\\begin_layout --Separator--", "", "\\end_layout", ""]
comert = ["\\begin_inset ERT", "status collapsed", "",
"\\begin_layout Plain Layout", "%", "\\end_layout",
"", "\\end_inset", ""]
empert = ["\\begin_inset ERT", "status collapsed", "",
"\\begin_layout Plain Layout", " ", "\\end_layout",
"", "\\end_inset", ""]
i = 0
while 1:
i = find_token(document.body, "\\begin_inset Separator", i)
if i == -1:
return
lay = get_containing_layout(document.body, i)
if lay == False:
document.warning("Malformed LyX document: Can't convert separator inset at line " + str(i))
i = i + 1
continue
layoutname = lay[0]
beg = lay[1]
end = lay[2]
kind = get_value(document.body, "\\begin_inset Separator", i, i+1, "plain").split()[1]
before = document.body[beg+1:i]
something_before = len(before) > 0 and len("".join(before)) > 0
j = find_end_of_inset(document.body, i)
after = document.body[j+1:end]
something_after = len(after) > 0 and len("".join(after)) > 0
if kind == "plain":
beg = beg + len(before) + 1
elif something_before:
document.body[i:i] = ["\\end_layout", ""]
i = i + 2
j = j + 2
beg = i
end = end + 2
if kind == "plain":
if something_after:
document.body[beg:j+1] = empert
i = i + len(empert)
else:
document.body[beg:j+1] = comert
i = i + len(comert)
else:
if something_after:
if layoutname == "Standard":
if not something_before:
document.body[beg:j+1] = parsep
i = i + len(parsep)
document.body[i:i] = ["", "\\begin_layout Standard"]
i = i + 2
else:
document.body[beg:j+1] = ["\\begin_layout Standard"]
i = i + 1
else:
document.body[beg:j+1] = ["\\begin_deeper"]
i = i + 1
end = end + 1 - (j + 1 - beg)
if not something_before:
document.body[i:i] = parsep
i = i + len(parsep)
end = end + len(parsep)
document.body[i:i] = ["\\begin_layout Standard"]
document.body[end+2:end+2] = ["", "\\end_deeper", ""]
i = i + 4
else:
next_par_is_aligned = False
k = find_nonempty_line(document.body, end+1)
if k != -1 and check_token(document.body[k], "\\begin_layout"):
lay = get_containing_layout(document.body, k)
next_par_is_aligned = lay != False and \
find_token(document.body, "\\align", lay[1], lay[2]) != -1
if k != -1 and not next_par_is_aligned \
and not check_token(document.body[k], "\\end_deeper") \
and not check_token(document.body[k], "\\begin_deeper"):
if layoutname == "Standard":
document.body[beg:j+1] = ["\\begin_layout --Separator--"]
i = i + 1
else:
document.body[beg:j+1] = ["\\begin_deeper", "\\begin_layout --Separator--"]
end = end + 2 - (j + 1 - beg)
document.body[end+1:end+1] = ["", "\\end_deeper", ""]
i = i + 3
else:
del document.body[i:end+1]
i = i + 1
##
# Conversion hub
#
supported_versions = ["2.2.0","2.2"]
convert = [#[475, []]
convert = [
[475, [convert_separator]],
]
revert = [#[474, []]
revert = [
[474, [revert_separator]]
]

View File

@ -165,6 +165,9 @@ import os, re, string, sys
# Incremented to format 49, 10 Feb 2014 by gb
# Change default of "ResetsFont" tag to false
# Incremented to format 50, 9 May 2014 by forenr
# Removal of "Separator" layouts
# Do not forget to document format change in Customization
# Manual (section "Declaring a new text class").
@ -172,7 +175,7 @@ import os, re, string, sys
# development/tools/updatelayouts.sh script to update all
# layout files to the new format.
currentFormat = 49
currentFormat = 50
def usage(prog_name):
@ -270,6 +273,7 @@ def convert(lines):
re_QInsetLayout2 = re.compile(r'^\s*InsetLayout\s+"([^"]+)"\s*$', re.IGNORECASE)
re_IsFlex = re.compile(r'\s*LyXType.*$', re.IGNORECASE)
re_CopyStyle2 = re.compile(r'(\s*CopyStyle\s+)"?([^"]+)"?\s*$')
re_Separator = re.compile(r'^(?:(-*)|(\s*))(Separator|EndOfSlide)(?:(-*)|(\s*))$', re.IGNORECASE)
# for categories
re_Declaration = re.compile(r'^#\s*\\Declare\w+Class.*$')
re_ExtractCategory = re.compile(r'^(#\s*\\Declare\w+Class(?:\[[^]]*?\])?){([^(]+?)\s+\(([^)]+?)\)\s*}\s*$')
@ -395,6 +399,63 @@ def convert(lines):
i += 1
continue
if format == 49:
separator = []
# delete separator styles
match = re_Style.match(lines[i])
if match:
style = string.lower(match.group(4))
if re_Separator.match(style):
del lines[i]
while i < len(lines) and not re_End.match(lines[i]):
separator.append(lines[i])
del lines[i]
if i == len(lines):
error('Incomplete separator style.')
else:
del lines[i]
continue
# delete undefinition of separator styles
match = re_NoStyle.match(lines[i])
if match:
style = string.lower(match.group(4))
if re_Separator.match(style):
del lines[i]
continue
# replace the CopyStyle statement with the definition of the real
# style. This may result in duplicate statements, but that is OK
# since the second one will overwrite the first one.
match = re_CopyStyle.match(lines[i])
if match:
style = string.lower(match.group(4))
if re_Separator.match(style):
if len(separator) > 0:
lines[i:i+1] = separator
else:
# FIXME: If this style was redefined in an include file,
# we should replace the real style and not this default.
lines[i:i+1] = [' Category MainText',
' KeepEmpty 1',
' Margin Dynamic',
' LatexType Paragraph',
' LatexName dummy',
' ParIndent MM',
' Align Block',
' LabelType Static',
' LabelString "--- Separate Environment ---"',
' LabelFont',
' Family Roman',
' Series Medium',
' Size Normal',
' Color Blue',
' EndFont',
' HTMLLabel NONE']
i += 1
continue
if format == 48:
# The default of ResetsFont in LyX changed from true to false,
# because it is now used for all InsetLayouts, not only flex ones.

View File

@ -301,6 +301,14 @@ Menuset
Item "Justified Line Break|J" "inset-modify newline linebreak"
End
#
# InsetNewline context menu
#
Menu "context-separator"
Item "Plain Separator|P" "inset-modify separator plain"
Item "Paragraph Break|B" "inset-modify separator parbreak"
End
#
# Edit context menu
#

View File

@ -108,6 +108,7 @@ void Bidi::computeTables(Paragraph const & par,
pos_type const pos =
(is_space && lpos + 1 <= end_ &&
!par.isLineSeparator(lpos + 1) &&
!par.isEnvSeparator(lpos + 1) &&
!par.isNewline(lpos + 1))
? lpos + 1 : lpos;

View File

@ -457,6 +457,7 @@ enum FuncCode
LFUN_VC_COPY, // gb 20130205
// 355
LFUN_SPELLING_CONTINUOUSLY, // vfr, 20130324
LFUN_SEPARATOR_INSERT, // ef 20140502
LFUN_LASTACTION // end of the table
};

View File

@ -625,6 +625,15 @@ void LyXAction::init()
* \endvar
*/
{ LFUN_NEWLINE_INSERT, "newline-insert", Noop, Edit },
/*!
* \var lyx::FuncCode lyx::LFUN_SEPARATOR_INSERT
* \li Action: Inserts an environment separator or paragraph break.
* \li Syntax: separator-insert [<ARG>]
* \li Params: <ARG>: <plain|parbreak> default: plain
* \li Origin: ef, 2 May 2014
* \endvar
*/
{ LFUN_SEPARATOR_INSERT, "separator-insert", Noop, Edit },
/*!
* \var lyx::FuncCode lyx::LFUN_ESCAPE
* \li Action: Clears the selection. If no text is selected call #LFUN_FINISHED_FORWARD.

View File

@ -575,6 +575,7 @@ SOURCEFILESINSETS = \
insets/InsetQuotes.cpp \
insets/InsetRef.cpp \
insets/InsetScript.cpp \
insets/InsetSeparator.cpp \
insets/InsetSpace.cpp \
insets/InsetSpecialChar.cpp \
insets/InsetTabular.cpp \
@ -633,6 +634,7 @@ HEADERFILESINSETS = \
insets/InsetQuotes.h \
insets/InsetRef.h \
insets/InsetScript.h \
insets/InsetSeparator.h \
insets/InsetSpace.h \
insets/InsetSpecialChar.h \
insets/InsetTabular.h \

View File

@ -1054,7 +1054,7 @@ void Paragraph::Private::latexInset(BufferParams const & bparams,
}
// FIXME: move this to InsetNewline::latex
if (inset->lyxCode() == NEWLINE_CODE) {
if (inset->lyxCode() == NEWLINE_CODE || inset->lyxCode() == SEPARATOR_CODE) {
// newlines are handled differently here than
// the default in simpleTeXSpecialChars().
if (!style.newline_allowed) {
@ -2132,17 +2132,17 @@ void Paragraph::setBeginOfBody()
// remove unnecessary getChar() calls
pos_type i = 0;
pos_type end = size();
if (i < end && !isNewline(i)) {
if (i < end && !(isNewline(i) || isEnvSeparator(i))) {
++i;
char_type previous_char = 0;
char_type temp = 0;
if (i < end) {
previous_char = d->text_[i];
if (!isNewline(i)) {
if (!(isNewline(i) || isEnvSeparator(i))) {
++i;
while (i < end && previous_char != ' ') {
temp = d->text_[i];
if (isNewline(i))
if (isNewline(i) || isEnvSeparator(i))
break;
++i;
previous_char = temp;
@ -3205,6 +3205,13 @@ bool Paragraph::isNewline(pos_type pos) const
}
bool Paragraph::isEnvSeparator(pos_type pos) const
{
Inset const * inset = getInset(pos);
return inset && inset->lyxCode() == SEPARATOR_CODE;
}
bool Paragraph::isLineSeparator(pos_type pos) const
{
char_type const c = d->text_[pos];

View File

@ -404,6 +404,8 @@ public:
bool isInset(pos_type pos) const;
///
bool isNewline(pos_type pos) const;
///
bool isEnvSeparator(pos_type pos) const;
/// return true if the char is a word separator
bool isSeparator(pos_type pos) const;
///

View File

@ -269,7 +269,7 @@ bool ParagraphMetrics::hfillExpansion(Row const & row, pos_type pos) const
// the specified position that is neither a newline nor an hfill,
// the hfill will be expanded, otherwise it won't
for (pos_type i = row.pos(); i < pos; i++) {
if (!par_->isNewline(i) && !par_->isHfill(i))
if (!par_->isNewline(i) && !par_->isEnvSeparator(i) && !par_->isHfill(i))
return true;
}
return false;

View File

@ -921,6 +921,7 @@ void Text::insertChar(Cursor & cur, char_type c)
if (contains(number_unary_operators, c) &&
(cur.pos() == 1
|| par.isSeparator(cur.pos() - 2)
|| par.isEnvSeparator(cur.pos() - 2)
|| par.isNewline(cur.pos() - 2))
) {
setCharFont(pit, cur.pos() - 1, cur.current_font,

View File

@ -656,6 +656,7 @@ bool Text::cursorBackward(Cursor & cur)
cur.textRow().pos() == cur.pos() &&
!cur.paragraph().isLineSeparator(cur.pos() - 1) &&
!cur.paragraph().isNewline(cur.pos() - 1) &&
!cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
!cur.paragraph().isSeparator(cur.pos() - 1)) {
return setCursor(cur, cur.pit(), cur.pos(), true, true);
}
@ -669,8 +670,14 @@ bool Text::cursorBackward(Cursor & cur)
}
// move to the previous paragraph or do nothing
if (cur.pit() > 0)
return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
if (cur.pit() > 0) {
Paragraph & par = getPar(cur.pit() - 1);
pos_type lastpos = par.size();
if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
else
return setCursor(cur, cur.pit() - 1, lastpos, true, false);
}
return false;
}
@ -734,12 +741,19 @@ bool Text::cursorForward(Cursor & cur)
bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
}
#endif
if (cur.textRow().endpos() == cur.pos() + 1 &&
cur.textRow().endpos() != cur.lastpos() &&
!cur.paragraph().isNewline(cur.pos()) &&
!cur.paragraph().isLineSeparator(cur.pos()) &&
!cur.paragraph().isSeparator(cur.pos())) {
return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
if (cur.textRow().endpos() == cur.pos() + 1) {
if (cur.paragraph().isEnvSeparator(cur.pos()) &&
cur.pos() + 1 == cur.lastpos() &&
cur.pit() != cur.lastpit()) {
// move to next paragraph
return setCursor(cur, cur.pit() + 1, 0, true, false);
} else if (cur.textRow().endpos() != cur.lastpos() &&
!cur.paragraph().isNewline(cur.pos()) &&
!cur.paragraph().isEnvSeparator(cur.pos()) &&
!cur.paragraph().isLineSeparator(cur.pos()) &&
!cur.paragraph().isSeparator(cur.pos())) {
return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
}
}
// in front of RTL boundary? Stay on this side of the boundary because:

View File

@ -1021,11 +1021,20 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
case LFUN_CHAR_DELETE_FORWARD:
if (!cur.selection()) {
bool was_separator = cur.paragraph().isEnvSeparator(cur.pos());
if (cur.pos() == cur.paragraph().size())
// Par boundary, force full-screen update
singleParUpdate = false;
needsUpdate |= erase(cur);
cur.resetAnchor();
if (was_separator && cur.pos() == cur.paragraph().size()
&& (!cur.paragraph().layout().isEnvironment()
|| cur.paragraph().size() > 0)) {
// Force full-screen update
singleParUpdate = false;
needsUpdate |= erase(cur);
cur.resetAnchor();
}
// It is possible to make it a lot faster still
// just comment out the line below...
} else {
@ -1038,11 +1047,17 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
case LFUN_CHAR_DELETE_BACKWARD:
if (!cur.selection()) {
if (bv->getIntl().getTransManager().backspace()) {
bool par_boundary = cur.pos() == 0;
// Par boundary, full-screen update
if (cur.pos() == 0)
if (par_boundary)
singleParUpdate = false;
needsUpdate |= backspace(cur);
cur.resetAnchor();
if (par_boundary && cur.pos() > 0
&& cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
needsUpdate |= backspace(cur);
cur.resetAnchor();
}
// It is possible to make it a lot faster still
// just comment out the line below...
}
@ -1052,11 +1067,33 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
}
break;
case LFUN_PARAGRAPH_BREAK:
case LFUN_PARAGRAPH_BREAK: {
cap::replaceSelection(cur);
breakParagraph(cur, cmd.argument() == "inverse");
pit_type pit = cur.pit();
Paragraph const & par = pars_[pit];
Paragraph const & prevpar = pit > 0 ? pars_[pit - 1] : par;
if (pit > 0 && cur.pos() == par.beginOfBody()
&& ((prevpar.getDepth() > par.getDepth()
&& !par.layout().isEnvironment())
|| (prevpar.layout() != par.layout()
&& prevpar.layout().isEnvironment()))) {
if (par.layout().isEnvironment()) {
docstring const layout = par.layout().name();
DocumentClass const & tc = bv->buffer().params().documentClass();
lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "parbreak"));
breakParagraph(cur, true);
lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
} else {
lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "parbreak"));
breakParagraph(cur);
}
} else {
breakParagraph(cur, cmd.argument() == "inverse");
}
cur.resetAnchor();
break;
}
case LFUN_INSET_INSERT: {
cur.recordUndo();
@ -1404,15 +1441,10 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
while (cur.paragraph().params().depth() > split_depth)
lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
}
bool const morecont = cur.lastpos() > cur.pos();
// FIXME This hardcoding is bad
docstring const sep =
cur.buffer()->params().documentClass().hasLayout(from_ascii("Separator"))
? from_ascii("Separator") : from_ascii("--Separator--");
lyx::dispatch(FuncRequest(LFUN_LAYOUT, sep));
DocumentClass const & tc = bv->buffer().params().documentClass();
lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
if (morecont)
lyx::dispatch(FuncRequest(LFUN_DOWN));
lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
break;
@ -1903,6 +1935,16 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
cur.posForward();
break;
case LFUN_SEPARATOR_INSERT: {
doInsertInset(cur, this, cmd, false, false);
cur.posForward();
// remove a following space
Paragraph & par = cur.paragraph();
if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
break;
}
case LFUN_DEPTH_DECREMENT:
changeDepth(cur, DEC_DEPTH);
break;
@ -2906,6 +2948,11 @@ bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
&& cur.pos() > cur.paragraph().beginOfBody();
break;
case LFUN_SEPARATOR_INSERT:
// Always enabled for now
enable = true;
break;
case LFUN_TAB_INSERT:
case LFUN_TAB_DELETE:
enable = cur.paragraph().isPassThru();
@ -2953,12 +3000,6 @@ bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
break;
case LFUN_ENVIRONMENT_SPLIT: {
// FIXME This hardcoding is bad
if (!cur.buffer()->params().documentClass().hasLayout(from_ascii("Separator"))
&& !cur.buffer()->params().documentClass().hasLayout(from_ascii("--Separator--"))) {
enable = false;
break;
}
if (cmd.argument() == "outer") {
// check if we have an environment in our nesting hierarchy
bool res = false;

View File

@ -61,7 +61,7 @@ namespace lyx {
// development/tools/updatelayouts.sh script, to update the format of
// all of our layout files.
//
int const LAYOUT_FORMAT = 49; //gb: change default of ResetsFont
int const LAYOUT_FORMAT = 50; //ef: removal of separator layout
namespace {

View File

@ -348,6 +348,7 @@ bool TextMetrics::isRTLBoundary(pit_type pit, pos_type pos,
// FED FED| FED )
if (startpos == pos && endpos == pos && endpos != par.size()
&& (par.isNewline(pos - 1)
|| par.isEnvSeparator(pos - 1)
|| par.isLineSeparator(pos - 1)
|| par.isSeparator(pos - 1)))
return false;
@ -628,6 +629,7 @@ void TextMetrics::computeRowMetrics(pit_type const pit,
if (ns
&& row.endpos() < par.size()
&& !par.isNewline(row.endpos() - 1)
&& !par.isEnvSeparator(row.endpos() - 1)
&& !disp_inset
) {
row.separator = w / ns;
@ -897,7 +899,7 @@ pos_type TextMetrics::rowBreakPoint(int width, pit_type const pit,
break;
}
if (par.isNewline(i)) {
if (par.isNewline(i) || par.isEnvSeparator(i)) {
point = i + 1;
break;
}
@ -1269,7 +1271,7 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
if (lastrow &&
((rtl_on_lastrow && left_side && vc == row.pos() && x < tmpx - 5) ||
(!rtl_on_lastrow && !left_side && vc == end && x > tmpx + 5))) {
if (!par.isNewline(end - 1))
if (!(par.isNewline(end - 1) || par.isEnvSeparator(end - 1)))
c = end;
} else if (vc == row.pos()) {
c = bidi.vis2log(vc);
@ -1318,7 +1320,9 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
if (!c || end == par.size())
return col;
if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
if (c==end && !par.isLineSeparator(c-1)
&& !par.isNewline(c-1)
&& !par.isEnvSeparator(c-1)) {
boundary = true;
return col;
}
@ -1810,7 +1814,8 @@ bool TextMetrics::cursorEnd(Cursor & cur)
bool boundary = false;
if (end != cur.lastpos()) {
if (!cur.paragraph().isLineSeparator(end-1)
&& !cur.paragraph().isNewline(end-1))
&& !cur.paragraph().isNewline(end-1)
&& !cur.paragraph().isEnvSeparator(end-1))
boundary = true;
else
--end;

View File

@ -52,6 +52,7 @@
#include "insets/InsetPreview.h"
#include "insets/InsetRef.h"
#include "insets/InsetScript.h"
#include "insets/InsetSeparator.h"
#include "insets/InsetSpace.h"
#include "insets/InsetTabular.h"
#include "insets/InsetTOC.h"
@ -99,6 +100,20 @@ Inset * createInsetHelper(Buffer * buf, FuncRequest const & cmd)
return new InsetNewpage(inp);
}
case LFUN_SEPARATOR_INSERT: {
string const name = cmd.getArg(0);
InsetSeparatorParams inp;
if (name.empty() || name == "plain")
inp.kind = InsetSeparatorParams::PLAIN;
else if (name == "parbreak")
inp.kind = InsetSeparatorParams::PARBREAK;
else {
lyxerr << "Wrong argument for LyX function 'separator-insert'." << endl;
break;
}
return new InsetSeparator(inp);
}
case LFUN_FLEX_INSERT: {
string s = cmd.getArg(0);
return new InsetFlex(buf, s);
@ -627,6 +642,8 @@ Inset * readInset(Lexer & lex, Buffer * buf)
inset.reset(new InsetNewpage);
} else if (tmptok == "Newline") {
inset.reset(new InsetNewline);
} else if (tmptok == "Separator") {
inset.reset(new InsetSeparator);
} else if (tmptok == "Argument") {
inset.reset(new InsetArgument(buf, tmptok));
} else if (tmptok == "Float") {

View File

@ -0,0 +1,274 @@
/**
* \file InsetSeparator.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Enrico Forestieri
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "InsetSeparator.h"
#include "Cursor.h"
#include "Dimension.h"
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "Lexer.h"
#include "MetricsInfo.h"
#include "OutputParams.h"
#include "output_xhtml.h"
#include "frontends/Application.h"
#include "frontends/FontMetrics.h"
#include "frontends/Painter.h"
#include "support/debug.h"
#include "support/docstream.h"
#include "support/docstring.h"
using namespace std;
namespace lyx {
InsetSeparator::InsetSeparator() : Inset(0)
{}
InsetSeparator::InsetSeparator(InsetSeparatorParams const & params)
: Inset(0), params_(params)
{}
void InsetSeparatorParams::write(ostream & os) const
{
string command;
switch (kind) {
case InsetSeparatorParams::PLAIN:
os << "plain";
break;
case InsetSeparatorParams::PARBREAK:
os << "parbreak";
break;
}
}
void InsetSeparatorParams::read(Lexer & lex)
{
string token;
lex.setContext("InsetSeparatorParams::read");
lex >> token;
if (token == "plain")
kind = InsetSeparatorParams::PLAIN;
else if (token == "parbreak")
kind = InsetSeparatorParams::PARBREAK;
else
lex.printError("Unknown kind: `$$Token'");
}
void InsetSeparator::write(ostream & os) const
{
os << "Separator ";
params_.write(os);
}
void InsetSeparator::read(Lexer & lex)
{
params_.read(lex);
lex >> "\\end_inset";
}
void InsetSeparator::doDispatch(Cursor & cur, FuncRequest & cmd)
{
switch (cmd.action()) {
case LFUN_INSET_MODIFY: {
InsetSeparatorParams params;
cur.recordUndo();
string2params(to_utf8(cmd.argument()), params);
params_.kind = params.kind;
break;
}
default:
Inset::doDispatch(cur, cmd);
break;
}
}
bool InsetSeparator::getStatus(Cursor & cur, FuncRequest const & cmd,
FuncStatus & status) const
{
switch (cmd.action()) {
// we handle these
case LFUN_INSET_MODIFY:
if (cmd.getArg(0) == "plain") {
InsetSeparatorParams params;
string2params(to_utf8(cmd.argument()), params);
status.setOnOff(params_.kind == params.kind);
}
status.setEnabled(true);
return true;
default:
return Inset::getStatus(cur, cmd, status);
}
}
ColorCode InsetSeparator::ColorName() const
{
switch (params_.kind) {
case InsetSeparatorParams::PLAIN:
return Color_latex;
break;
case InsetSeparatorParams::PARBREAK:
return Color_pagebreak;
break;
}
// not really useful, but to avoids gcc complaints
return Color_latex;
}
void InsetSeparator::latex(otexstream & os, OutputParams const & rp) const
{
switch (params_.kind) {
case InsetSeparatorParams::PLAIN:
os << breakln << "%\n";
break;
case InsetSeparatorParams::PARBREAK:
os << breakln << "\n";
break;
default:
os << breakln << "%\n";
break;
}
}
int InsetSeparator::plaintext(odocstringstream & os,
OutputParams const &, size_t) const
{
os << '\n';
return PLAINTEXT_NEWLINE;
}
int InsetSeparator::docbook(odocstream & os, OutputParams const &) const
{
os << '\n';
return 0;
}
docstring InsetSeparator::xhtml(XHTMLStream & xs, OutputParams const &) const
{
xs << html::CR() << html::CompTag("br") << html::CR();
return docstring();
}
void InsetSeparator::metrics(MetricsInfo & mi, Dimension & dim) const
{
frontend::FontMetrics const & fm = theFontMetrics(mi.base.font);
dim.asc = fm.maxAscent();
dim.des = fm.maxDescent();
dim.wid = fm.width('m');
if (params_.kind == InsetSeparatorParams::PLAIN)
dim.wid *= 5;
}
void InsetSeparator::draw(PainterInfo & pi, int x, int y) const
{
FontInfo font;
font.setColor(ColorName());
frontend::FontMetrics const & fm = theFontMetrics(pi.base.font);
int const wid = fm.width('m');
int const asc = fm.maxAscent();
int xp[3];
int yp[3];
if (params_.kind == InsetSeparatorParams::PLAIN) {
yp[0] = int(y - 0.500 * asc * 0.75);
yp[1] = int(y - 0.500 * asc * 0.75);
xp[0] = int(x);
xp[1] = int(x + wid * 5);
pi.pain.lines(xp, yp, 2, ColorName());
} else {
yp[0] = int(y - 0.875 * asc * 0.75);
yp[1] = int(y - 0.500 * asc * 0.75);
yp[2] = int(y - 0.125 * asc * 0.75);
if (pi.ltr_pos) {
xp[0] = int(x + wid * 0.375);
xp[1] = int(x);
xp[2] = int(x + wid * 0.375);
} else {
xp[0] = int(x + wid * 0.625);
xp[1] = int(x + wid);
xp[2] = int(x + wid * 0.625);
}
pi.pain.lines(xp, yp, 3, ColorName());
yp[0] = int(y - 0.500 * asc * 0.75);
yp[1] = int(y - 0.500 * asc * 0.75);
yp[2] = int(y - asc * 0.75);
if (pi.ltr_pos) {
xp[0] = int(x);
xp[1] = int(x + wid);
xp[2] = int(x + wid);
} else {
xp[0] = int(x + wid);
xp[1] = int(x);
xp[2] = int(x);
}
pi.pain.lines(xp, yp, 3, ColorName());
}
}
string InsetSeparator::contextMenuName() const
{
return "context-separator";
}
void InsetSeparator::string2params(string const & in, InsetSeparatorParams & params)
{
params = InsetSeparatorParams();
if (in.empty())
return;
istringstream data(in);
Lexer lex;
lex.setStream(data);
lex.setContext("InsetSeparator::string2params");
lex >> "separator";
params.read(lex);
}
string InsetSeparator::params2string(InsetSeparatorParams const & params)
{
ostringstream data;
data << "separator" << ' ';
params.write(data);
return data.str();
}
} // namespace lyx

View File

@ -0,0 +1,95 @@
// -*- C++ -*-
/**
* \file InsetSeparator.h
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Enrico Forestieri
*
* Full author contact details are available in file CREDITS.
*/
#ifndef INSET_SEPARATOR_H
#define INSET_SEPARATOR_H
#include "Inset.h"
namespace lyx {
class InsetSeparatorParams
{
public:
/// The different kinds of separators we support
enum Kind {
///
PLAIN,
///
PARBREAK
};
///
InsetSeparatorParams() : kind(PLAIN) {}
///
void write(std::ostream & os) const;
///
void read(Lexer & lex);
///
Kind kind;
};
class InsetSeparator : public Inset
{
public:
///
InsetSeparator();
///
explicit InsetSeparator(InsetSeparatorParams const & par);
///
static void string2params(std::string const &, InsetSeparatorParams &);
///
static std::string params2string(InsetSeparatorParams const &);
private:
///
InsetSeparatorParams params() const { return params_; }
///
InsetCode lyxCode() const { return SEPARATOR_CODE; }
///
void metrics(MetricsInfo &, Dimension &) const;
///
void draw(PainterInfo & pi, int x, int y) const;
///
void latex(otexstream &, OutputParams const &) const;
///
int plaintext(odocstringstream & ods, OutputParams const & op,
size_t max_length = INT_MAX) const;
///
int docbook(odocstream &, OutputParams const &) const;
///
docstring xhtml(XHTMLStream &, OutputParams const &) const;
///
void read(Lexer & lex);
///
void write(std::ostream & os) const;
/// is this equivalent to a space (which is BTW different from
/// a line separator)?
bool isSpace() const { return true; }
///
ColorCode ColorName() const;
///
std::string contextMenuName() const;
///
Inset * clone() const { return new InsetSeparator(*this); }
///
void doDispatch(Cursor & cur, FuncRequest & cmd);
///
bool getStatus(Cursor & cur, FuncRequest const & cmd, FuncStatus &) const;
///
InsetSeparatorParams params_;
};
} // namespace lyx
#endif // INSET_SEPARATOR_H

View File

@ -213,7 +213,8 @@ static void finishEnvironment(otexstream & os, OutputParams const & runparams,
}
if (data.style->isEnvironment()) {
os << "\\end{" << from_ascii(data.style->latexname()) << "}\n";
os << breakln
<< "\\end{" << from_ascii(data.style->latexname()) << "}\n";
prev_env_language_ = data.par_language;
if (runparams.encoding != data.prev_encoding) {
runparams.encoding = data.prev_encoding;
@ -223,7 +224,7 @@ static void finishEnvironment(otexstream & os, OutputParams const & runparams,
}
if (data.leftindent_open) {
os << "\\end{LyXParagraphLeftIndent}\n";
os << breakln << "\\end{LyXParagraphLeftIndent}\n";
prev_env_language_ = data.par_language;
if (runparams.encoding != data.prev_encoding) {
runparams.encoding = data.prev_encoding;
@ -277,21 +278,6 @@ void TeXEnvironment(Buffer const & buf, Text const & text,
// Or par->params().depth() > current_depth
// Or par->params().leftIndent() != current_left_indent)
if (par->layout().isParagraph()) {
// FIXME (Lgb): How to handle this?
//&& !suffixIs(os, "\n\n")
// (ARRae) There should be at least one '\n' already but we need there to
// be two for Standard paragraphs that are depth-increment'ed to be
// output correctly. However, tables can also be paragraphs so
// don't adjust them.
// FIXME (Lgb): Will it ever harm to have one '\n' too
// many? i.e. that we sometimes will have
// three in a row.
os << '\n';
}
// FIXME This test should not be necessary.
// We should perhaps issue an error if it is.
bool const force_plain_layout = text.inset().forcePlainLayout();
@ -302,6 +288,21 @@ void TeXEnvironment(Buffer const & buf, Text const & text,
if (!style.isEnvironment()) {
// This is a standard paragraph, no need to call TeXEnvironment.
TeXOnePar(buf, text, pit, os, runparams);
// Unless the current or following paragraph are inside
// \begin..\end tags and the nesting layout is not of
// an itemize kind, we have to output a paragraph break
// (we already are at the beginning of a new line)
if (pit + 1 < runparams.par_end) {
ParagraphList::const_iterator nextpar =
paragraphs.constIterator(pit + 1);
if (nextpar->layout() == current_layout
&& nextpar->getDepth() == current_depth
&& current_layout.latextype != LATEX_ITEM_ENVIRONMENT
&& current_layout.latextype != LATEX_LIST_ENVIRONMENT
&& par->getAlign() == style.align
&& nextpar->getAlign() == nextpar->layout().align)
os << '\n';
}
continue;
}
@ -858,7 +859,9 @@ void TeXOnePar(Buffer const & buf,
switch (style.latextype) {
case LATEX_ITEM_ENVIRONMENT:
case LATEX_LIST_ENVIRONMENT:
if (nextpar && (par.params().depth() < nextpar->params().depth()))
if (nextpar
&& (par.params().depth() < nextpar->params().depth())
&& !par.isEnvSeparator(par.size() - 1))
pending_newline = true;
break;
case LATEX_ENVIRONMENT: {
@ -873,7 +876,8 @@ void TeXOnePar(Buffer const & buf,
// fall through possible
default:
// we don't need it for the last paragraph!!!
if (nextpar)
// or if the last thing is an environment separator
if (nextpar && !par.isEnvSeparator(par.size() - 1))
pending_newline = true;
}
@ -882,7 +886,13 @@ void TeXOnePar(Buffer const & buf,
&& (runparams.isLastPar || !nextpar->hasSameLayout(par))) {
if (pending_newline)
os << '\n';
os << from_ascii(par.params().spacing().writeEnvirEnd(useSetSpace));
string const endtag =
par.params().spacing().writeEnvirEnd(useSetSpace);
if (prefixIs(endtag, "\\end{"))
os << breakln;
os << from_ascii(endtag);
pending_newline = true;
}
}
@ -1013,19 +1023,29 @@ void TeXOnePar(Buffer const & buf,
// we don't need a newline for the last paragraph!!!
// Note from JMarc: we will re-add a \n explicitly in
// TeXEnvironment, because it is needed in this case
if (nextpar) {
if (nextpar && !par.isEnvSeparator(par.size() - 1)) {
// Make sure to start a new line
os << breakln;
// Here we now try to avoid spurious empty lines by outputting
// a paragraph break only if: (case 1) the paragraph style
// allows parbreaks and no \begin, \end or \item tags are
// going to follow (i.e., if the next isn't the first
// or the current isn't the last paragraph of an environment
// or itemize) and the depth and alignment of the following
// paragraph is unchanged, or (case 2) the following is a
// non-environment paragraph whose depth is increased but
// whose alignment is unchanged.
Layout const & next_layout = nextpar->layout();
if (style == next_layout
// no blank lines before environments!
|| !next_layout.isEnvironment()
// unless there's a depth change
// FIXME What we really want to do here is put every \begin and \end
// tag on a new line (which was not the case with nested environments).
// But in the present state of play, we don't have access to the
// information whether the current TeX row is empty or not.
// For some ideas about how to fix this, see this thread:
// http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg145787.html
|| nextpar->params().depth() != par.params().depth()) {
if ((style == next_layout
&& !style.parbreak_is_newline
&& style.latextype != LATEX_ITEM_ENVIRONMENT
&& style.latextype != LATEX_LIST_ENVIRONMENT
&& style.align == par.getAlign()
&& nextpar->getDepth() == par.getDepth()
&& nextpar->getAlign() == par.getAlign())
|| (!next_layout.isEnvironment()
&& nextpar->getDepth() > par.getDepth()
&& nextpar->getAlign() == par.getAlign())) {
os << '\n';
}
}

View File

@ -1599,24 +1599,13 @@ void parse_environment(Parser & p, ostream & os, bool outer,
if (last_env == name) {
// we need to output a separator since LyX would export
// the two environments as one otherwise (bug 5716)
docstring const sep = from_ascii("--Separator--");
TeX2LyXDocClass const & textclass(parent_context.textclass);
if (textclass.hasLayout(sep)) {
Context newcontext(parent_context);
newcontext.layout = &(textclass[sep]);
newcontext.check_layout(os);
newcontext.check_end_layout(os);
} else {
parent_context.check_layout(os);
begin_inset(os, "Note Note\n");
os << "status closed\n";
Context newcontext(true, textclass,
&(textclass.defaultLayout()));
newcontext.check_layout(os);
newcontext.check_end_layout(os);
end_inset(os);
parent_context.check_end_layout(os);
}
Context newcontext(true, textclass,
&(textclass.defaultLayout()));
newcontext.check_layout(os);
begin_inset(os, "Separator plain\n");
end_inset(os);
newcontext.check_end_layout(os);
}
switch (context.layout->latextype) {
case LATEX_LIST_ENVIRONMENT:

View File

@ -30,8 +30,8 @@ extern char const * const lyx_version_info;
// Do not remove the comment below, so we get merge conflict in
// independent branches. Instead add your own.
#define LYX_FORMAT_LYX 474 // rgh: dummy format change for Chunk switch
#define LYX_FORMAT_TEX2LYX 474
#define LYX_FORMAT_LYX 475 // ef: new separator inset
#define LYX_FORMAT_TEX2LYX 475
#if LYX_FORMAT_TEX2LYX != LYX_FORMAT_LYX
#ifndef _MSC_VER