Viewing 1 post (of 1 total)
  • Author
    Posts
  • #30096
    gning
    Participant

    When you are editing text with a hard right margin, it helps to be able to reflow a paragraph to fit that width. The editor has the basics in it — steps where you can set a right margin and then convert soft newlines to hard ones — but not only is this a multistep process that would benefit from being encapsulated into a macro, it also produces paragraphs that don’t look nice. So I made a macro that not only encapsulates this reflow action, but also improves the aesthetics of the resulting paragraph.

    Here’s an example of what I mean. Here is a paragraph of HTML from a web page which has a link in it, which due to its width produces a line that doesn’t reflow well:
    before
    And here’s that paragraph with my improved reflow, which rebalances long and short lines to make things more even:
    after

    It specifically avoids reflowing on the space within an a href... HTML tag. It could be expanded to handle tags more generally, but I ran into difficulty selection.Replace when I tried to use regular expressions. And for that matter with using the Regex object itself. I ended up using JS’s built-in Regex.

    It also preserves the indent if a paragraph is indented.

    Here is the macro script, reflow selection with indent and smoothing.jsee:

    // NOTE: wrap width is set by Current Configuration / General
    
    if (document.selection.IsEmpty) {
        alert("Select a paragraph or paragraphs.\r\n(You haven't put in automatic selection yet.)");
        Quit();
    }
    
    // Need a separate reference var which stays consistent through the duration,
    // or making changes to the config values WILL NOT STICK.
    cfg = document.Config;
    
    // Save current wrap and indent settings, and selection endpoints:
    var stateNoWrap     = editor.QueryStatusByID(4208);                 // No Wrap
    var stateWindowWrap = editor.QueryStatusByID(4210);                 // Wrap by Window
    var statePageWrap   = editor.QueryStatusByID(4318);                 // Wrap by Page
    var oldMargin       = cfg.General.MarginNormal;
    var oldIC           = cfg.Indent.IndentColumns;
    var oldTC           = cfg.Indent.TabColumns;                        // Indent() actually uses this
    
    var anchorX = document.selection.GetAnchorPointX(eePosLogical);     // starting end of selection
    var anchorY = document.selection.GetAnchorPointY(eePosLogical);
    var activeX = document.selection.GetActivePointX(eePosLogical);     // cursor end of selection
    var activeY = document.selection.GetActivePointY(eePosLogical);
    
    // Determine current indent so margin can be adjusted, then restore selection:
    document.selection.StartOfLine(false, eeLineLogical | eeLineHomeText);
    var indentage = document.selection.GetActivePointX(eePosLogical) - 1;
    document.selection.SetAnchorPoint(eePosLogical, anchorX, anchorY);
    document.selection.SetActivePoint(eePosLogical, activeX, activeY, true);
    
    // Set Wrap by Characters mode so we can update margin and indent size:
    cfg.Indent.IndentColumns = cfg.Indent.TabColumns = 1;
    cfg.General.MarginNormal = oldMargin - indentage;
    cfg.Save();
    
    // Before we use the built-in reformatter, we want to protect <a href...> tags from being broken up.
    document.selection.Replace("<a href", "<a°href", eeFindReplaceSelOnly | eeFindReplaceQuiet | eeReplaceAll);   // regex mode not working
    
    // The reason we're here -- unindent, reflow, and reindent:
    document.selection.UnIndent(indentage);
    editor.ExecuteCommandByID(4209);
    document.selection.Format(eeFormatJoinLines);
    document.selection.Format(eeFormatSplitLines);
    
    // Re-get updated endpoints, which should now be in normalized order with anchor first:
    var anchorX = document.selection.GetAnchorPointX(eePosLogical);
    var anchorY = document.selection.GetAnchorPointY(eePosLogical);
    var activeX = document.selection.GetActivePointX(eePosLogical);
    var activeY = document.selection.GetActivePointY(eePosLogical);
    
    // Our secret sauce: after the built-in reflow is done, smooth it over by finding long lines
    // followed by short ones, and moving words down in order to even out the line lengths.
    var shorty = true;
    var motion = 0;
    var pass = 1;
    var rex = /\s+(\S+)\s*$/;             // Em's internal RegEx class didn't work at all
    for (var follow = activeY; follow > anchorY; follow--) {    // the line number being wrapped into
        document.selection.SetActivePoint(eePosLogical, 1, follow, false);
        document.selection.EndOfLine(false, eeLineLogical);
        var widthFol = document.selection.GetActivePointX(eePosLogical) - 1;
        document.selection.StartOfLine(false, eeLineLogical | eeLineHomeText);
        var dentFol = document.selection.GetActivePointX(eePosLogical) - 1;
        if (dentFol >= widthFol) {
            shorty = true;
            continue;           // consider deleting trailing space here (none should occur?)
        }
        document.selection.LineUp(false);
        document.selection.EndOfLine(false, eeLineLogical);
        var widthLead = document.selection.GetActivePointX(eePosLogical) - 1;
        if (widthLead <= dentFol) {
            follow--;
            shorty = true;
            continue;
        }
        document.selection.StartOfLine(true, eeLineLogical | eeLineHomeText);   // select lead line
        // extract final space-delimited word of leading line, and any whitespace next to it
        var mc = rex.exec(document.selection.Text);
        if (mc && mc[0] && widthLead - widthFol > mc[0].length && !shorty) {  // does word fit better below?
            var word = mc[0];
            motion += widthLead - widthFol;
            document.selection.EndOfLine(false, eeLineLogical);
            document.selection.DeleteLeft(word.length);
            document.selection.LineDown(false);
            document.selection.StartOfLine(false, eeLineLogical | eeLineHomeText);
            var mtrim = rex.exec(word);
            if (mtrim && mtrim[1])
                word = mtrim[1];
            document.selection.Text = word;
            document.selection.Text = " ";
            follow++;       // retry the same line with another word
        } else
            shorty = false;
        // In bad cases, do the whole smoothing operation again.  Usually each pass only finds about
        // a third as much work as was done last time.  We put a hard limit on passes just in case.
        if (follow - 1 <= anchorY && motion >= 16 && pass < 4) {
            shorty = true;
            pass++;
            follow = activeY;
        }
    }
    
    // Restore selection and indentation, and <a href...> tags:
    document.selection.SetAnchorPoint(eePosLogical, anchorX, anchorY);
    document.selection.SetActivePoint(eePosLogical, activeX, activeY, true);
    if (indentage > 0)      // if you pass 0 it still indents by 1
        document.selection.Indent(indentage);
    document.selection.Replace("<a°href", "<a href", eeFindReplaceSelOnly | eeFindReplaceQuiet | eeReplaceAll);
    
    // To clean up, restore previous wrap and indent settings:
    cfg.General.MarginNormal = oldMargin;
    cfg.Indent.IndentColumns = oldIC;
    cfg.Indent.TabColumns    = oldTC;
    cfg.Save();
    if (stateNoWrap >= eeStatusLatched)
        editor.ExecuteCommandByID(4208);
    else if (stateWindowWrap >= eeStatusLatched)
        editor.ExecuteCommandByID(4210);
    else if (statePageWrap >= eeStatusLatched)
        editor.ExecuteCommandByID(4318);
    // Done.
Viewing 1 post (of 1 total)
  • You must be logged in to reply to this topic.