"Fossies" - the Fresh Open Source Software Archive

Member "ne-3.2.1/src/edit.c" (30 Sep 2019, 25185 Bytes) of package /linux/misc/ne-3.2.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "edit.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 3.1.1_vs_3.1.2.

    1 /* Various editing functions such as word wrap, to upper, etc.
    2 
    3    Copyright (C) 1993-1998 Sebastiano Vigna
    4    Copyright (C) 1999-2019 Todd M. Lewis and Sebastiano Vigna
    5 
    6    This file is part of ne, the nice editor.
    7 
    8    This library is free software; you can redistribute it and/or modify it
    9    under the terms of the GNU General Public License as published by
   10    the Free Software Foundation; either version 3 of the License, or (at your
   11    option) any later version.
   12 
   13    This library is distributed in the hope that it will be useful, but
   14    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   15    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   16    for more details.
   17 
   18    You should have received a copy of the GNU General Public License
   19    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
   20 
   21 
   22 #include "ne.h"
   23 #include "support.h"
   24 
   25 /* The number of type of brackets we recognize. */
   26 #define NUM_BRACKETS 5
   27 
   28 /* Applies a given to_first() function to the first letter of the text starting at the cursor,
   29    and to_rest() to the following alphabetical letters (see the functions below). */
   30 
   31 static int to_something(buffer *b, int (to_first)(int), int (to_rest)(int)) {
   32     assert_buffer(b);
   33 
   34     /* If we are after the end of the line, just return ERROR. */
   35 
   36     if (b->cur_line == b->num_lines -1 && b->cur_pos >= b->cur_line_desc->line_len) return ERROR;
   37 
   38     int64_t pos = b->cur_pos;
   39     int c;
   40     /* First of all, we search for the word start, if we're not over it. */
   41     if (pos >= b->cur_line_desc->line_len || !ne_isword(c = get_char(&b->cur_line_desc->line[pos], b->encoding), b->encoding))
   42         if (search_word(b, 1, true) != OK)
   43             return ERROR;
   44 
   45     bool changed = false;
   46     int64_t new_len = 0;
   47     pos = b->cur_pos;
   48     const int64_t cur_char = b->cur_char;
   49     const int cur_x = b->cur_x;
   50     /* Then, we compute the word position extremes, length of the result (which
   51         may change because of casing). */
   52     while (pos < b->cur_line_desc->line_len && ne_isword(c = get_char(&b->cur_line_desc->line[pos], b->encoding), b->encoding)) {
   53         const int new_c = new_len ? to_rest(c) : to_first(c);
   54         changed |= (c != new_c);
   55 
   56         if (b->encoding == ENC_UTF8) new_len += utf8seqlen(new_c);
   57         else new_len++;
   58 
   59         pos = next_pos(b->cur_line_desc->line, pos, b->encoding);
   60     }
   61 
   62     const int64_t len = pos - b->cur_pos;
   63     if (!len) {
   64         char_right(b);
   65         return OK;
   66     }
   67 
   68     if (changed) {
   69         /* We actually perform changes only if some character was case folded. */
   70         char * word = malloc(new_len * sizeof *word);
   71         if (!word) return OUT_OF_MEMORY;
   72 
   73         pos = b->cur_pos;
   74         new_len = 0;
   75         /* Second pass: we actually build the transformed word. */
   76         while (pos < b->cur_line_desc->line_len && ne_isword(c = get_char(&b->cur_line_desc->line[pos], b->encoding), b->encoding)) {
   77             if (b->encoding == ENC_UTF8) new_len += utf8str(new_len ? to_rest(c) : to_first(c), word + new_len);
   78             else {
   79                 word[new_len] = new_len ? to_rest(c) : to_first(c);
   80                 new_len++;
   81             }
   82 
   83             pos = next_pos(b->cur_line_desc->line, pos, b->encoding);
   84         }
   85 
   86         start_undo_chain(b);
   87 
   88         delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, len);
   89         if (new_len) insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, word, new_len);
   90 
   91         free(word);
   92 
   93         end_undo_chain(b);
   94 
   95         if (cur_char < b->attr_len) b->attr_len = cur_char;
   96         update_line(b, b->cur_line_desc, b->cur_y, cur_x, false);
   97         need_attr_update = true;
   98     }
   99 
  100     return search_word(b, 1, true);
  101 }
  102 
  103 
  104 
  105 /* These functions upper case, lower case or capitalize the word the cursor is
  106    on. They just call to_something(). Note the parentheses around the function
  107    names, which inhibit the possible macros. */
  108 
  109 int to_upper(buffer *b) {
  110     return b->encoding == ENC_UTF8 ? to_something(b, (utf8toupper), (utf8toupper)) : to_something(b, (toupper), (toupper));
  111 }
  112 
  113 int to_lower(buffer *b) {
  114     return b->encoding == ENC_UTF8 ? to_something(b, (utf8tolower), (utf8tolower)) : to_something(b, (tolower), (tolower));
  115 }
  116 
  117 int capitalize(buffer *b) {
  118     return b->encoding == ENC_UTF8 ? to_something(b, (utf8toupper), (utf8tolower)) : to_something(b, (toupper), (tolower));
  119 }
  120 
  121 
  122 
  123 
  124 
  125 /* Finds which bracket matches the bracket under the cursor, and moves it
  126    there. Various error codes can be returned. */
  127 
  128 int match_bracket(buffer *b) {
  129     int64_t match_line, match_pos;
  130     const int rc = find_matching_bracket(b, 0, b->num_lines-1, &match_line, &match_pos, NULL, NULL);
  131     if (rc == OK) {
  132         goto_line_pos(b, match_line, match_pos);
  133         return OK;
  134     }
  135     return rc;
  136 }
  137 
  138 int find_matching_bracket(buffer *b, const int64_t min_line, int64_t max_line, int64_t *match_line, int64_t *match_pos, int *c, line_desc ** match_ld) {
  139 
  140     static unsigned char bracket_table[NUM_BRACKETS][2] = { { '(', ')'  },
  141                                                             { '[', ']'  },
  142                                                             { '{', '}'  },
  143                                                             { '<', '>'  },
  144                                                             { '`', '\'' } };
  145 
  146     line_desc *ld = b->cur_line_desc;
  147 
  148     if (b->cur_pos >= ld->line_len) return NOT_ON_A_BRACKET;
  149 
  150     int i, j, dir;
  151     for(i = 0; i < NUM_BRACKETS; i++) {
  152         for(j = 0; j < 2; j++)
  153             if (ld->line[b->cur_pos] == bracket_table[i][j]) break;
  154         if (j < 2) break;
  155     }
  156 
  157     if (i == NUM_BRACKETS && j == 2) return NOT_ON_A_BRACKET;
  158 
  159     if (j) dir = -1;
  160     else dir = 1;
  161 
  162     int n = 0;
  163     int64_t pos = b->cur_pos, y = b->cur_line;
  164 
  165     while(ld->ld_node.next && ld->ld_node.prev && y >= min_line && y <= max_line) {
  166 
  167         if (pos >= 0) {
  168             char * const line = ld->line;
  169             while(pos >= 0 && pos < ld->line_len) {
  170 
  171                 if (line[pos] == bracket_table[i][j]) n++;
  172                 else if (line[pos] == bracket_table[i][1 - j]) n--;
  173 
  174                 if (n == 0) {
  175                     *match_line = y;
  176                     *match_pos  = pos;
  177                     if (c) *c = line[pos];
  178                     if (match_ld) *match_ld = ld;
  179                     return OK;
  180                 }
  181                 if (dir > 0) pos = next_pos(line, pos, b->encoding);
  182                 else pos = prev_pos(line, pos, b->encoding);
  183             }
  184         }
  185 
  186         pos = -1;
  187 
  188         if (dir == 1) {
  189             ld = (line_desc *)ld->ld_node.next;
  190             if (ld->ld_node.next && ld->line) pos = 0;
  191             y++;
  192         }
  193         else {
  194             ld = (line_desc *)ld->ld_node.prev;
  195             if (ld->ld_node.prev && ld->line) pos = ld->line_len - 1;
  196             y--;
  197         }
  198     }
  199 
  200     return CANT_FIND_BRACKET;
  201 }
  202 
  203 
  204 
  205 /* Breaks a line at the first possible position before the current cursor
  206    position (i.e., at a tab or at a space). The space is deleted, and a
  207    new line is inserted. The cursor is repositioned coherently. The number
  208    of bytes existing on the new line is returned, or ERROR if no word
  209    wrap was possible. */
  210 
  211 int64_t word_wrap(buffer * const b) {
  212     const int64_t len = b->cur_line_desc->line_len;
  213     char * const line = b->cur_line_desc->line;
  214     int64_t pos, first_pos;
  215 
  216     if (!(pos = b->cur_pos)) return ERROR;
  217 
  218     /* Find the first possible position we could break a line on. */
  219     first_pos = 0;
  220 
  221     /* Skip leading white space */
  222     while (first_pos < len && ne_isspace(get_char(&line[first_pos], b->encoding), b->encoding)) first_pos = next_pos(line, first_pos, b->encoding);
  223 
  224     /* Skip non_space after leading white space */
  225     while (first_pos < len && !ne_isspace(get_char(&line[first_pos], b->encoding), b->encoding)) first_pos = next_pos(line, first_pos, b->encoding);
  226 
  227     /* Now we know that the line shouldn't be broken before &line[first_pos]. */
  228 
  229     /* Search left from the current position to find a candidate space to break the line on.*/
  230     while((pos = prev_pos(line, pos, b->encoding)) && !ne_isspace(get_char(&line[pos], b->encoding), b->encoding));
  231 
  232     if (! pos || pos < first_pos) return ERROR;
  233 
  234     start_undo_chain(b);
  235 
  236     const int64_t result = b->cur_pos - pos - 1;
  237     if (pos < b->cur_pos) b->cur_pos = -1;
  238     delete_one_char(b, b->cur_line_desc, b->cur_line, pos);
  239     insert_one_line(b, b->cur_line_desc, b->cur_line, pos);
  240 
  241     end_undo_chain(b);
  242 
  243     return result;
  244 }
  245 
  246 
  247 
  248 /* This experimental alternative to word wrapping sets a bookmark, calls
  249    paragraph(), then returns to the bookmark (which may have moved due to
  250    insertions/deletions).  The number of characters existing on the new line is
  251    returned, or ERROR if no word wrap was possible. */
  252 
  253 int word_wrap2(buffer * const b) {
  254     static char avcmd[16];
  255 
  256     if (b->cur_pos > b->cur_line_desc->line_len) return OK;
  257 
  258     bool non_blank_added = false;
  259     int avshift;
  260     char * line = b->cur_line_desc->line;
  261     int64_t pos, original_line;
  262 
  263     /* If the char to our left is a space, we need to insert
  264        a non-space to attach our WORDWRAP_BOOKMARK to because
  265        spaces at the split point get removed, which effectively
  266        leaves our bookmark on the current line. */
  267     delay_update();
  268     pos = prev_pos(line, b->cur_pos, b->encoding);
  269     if (pos >= 0 && (non_blank_added = ne_isspace(get_char(&line[pos], b->encoding), b->encoding))) {
  270         start_undo_chain(b);
  271         insert_one_char(b, b->cur_line_desc, b->cur_line, b->cur_pos, 'X');
  272         line = b->cur_line_desc->line;
  273         goto_pos(b, next_pos(line, b->cur_pos, b->encoding));
  274     }
  275     b->bookmark[WORDWRAP_BOOKMARK].pos   = b->cur_pos;
  276     b->bookmark[WORDWRAP_BOOKMARK].line  = original_line = b->cur_line;
  277     b->bookmark[WORDWRAP_BOOKMARK].cur_y = b->cur_y;
  278     b->bookmark_mask |= (1 << WORDWRAP_BOOKMARK);
  279     paragraph(b);
  280     goto_line_pos(b, b->bookmark[WORDWRAP_BOOKMARK].line, b->bookmark[WORDWRAP_BOOKMARK].pos);
  281     line = b->cur_line_desc->line;
  282     b->bookmark[WORDWRAP_BOOKMARK].cur_y += b->bookmark[WORDWRAP_BOOKMARK].line - original_line;
  283     if (avshift = b->cur_y - b->bookmark[WORDWRAP_BOOKMARK].cur_y) {
  284         snprintf(avcmd, 16, "%c%d", avshift > 0 ? 'T' :'B', avshift > 0 ? avshift : -avshift);
  285         adjust_view(b, avcmd);
  286     }
  287     b->bookmark_mask &= ~(1 << WORDWRAP_BOOKMARK);
  288     if (non_blank_added) {
  289         goto_pos(b, prev_pos(b->cur_line_desc->line, b->cur_pos, b->encoding));
  290         delete_one_char(b, b->cur_line_desc, b->cur_line, b->cur_pos);
  291         end_undo_chain(b);
  292     }
  293     return stop ? STOPPED : OK;
  294 }
  295 
  296 /* These functions reformat a paragraph while preserving appropriate
  297    leading US-ASCII white space. */
  298 
  299 static char   *pa_space;     /* Where we keep space for paragraph left offsets */
  300 static int64_t pa_space_len; /* How long pa_space is when tabs are expanded */
  301 static int64_t pa_space_pos; /* How long pa_space is without expanding tabs */
  302 
  303 /* save_space() sets pa_space, pa_space_len, and pa_space_pos to reflect the
  304    space on the left end of the line ld refers to in the context of the given
  305    tab size. If the line contains only space then it is treated identically to
  306    an empty line, in which case save_space() returns 0 and pa_space,
  307    pa_space_len, and pa_space_pos are cleared. Otherwise it returns 1. The
  308    string pa_space points to is not null-terminated, so be careful how you use it. */
  309 
  310 static int save_space(line_desc * const ld, const int tab_size, const encoding_type encoding) {
  311     if (pa_space) free(pa_space);
  312     pa_space  = NULL;
  313     pa_space_len = 0;
  314     pa_space_pos = 0;
  315 
  316     if (!ld->line) return 0; /* No data on this line. */
  317 
  318     int64_t pos = 0;
  319     while(pos < ld->line_len && isasciispace(ld->line[pos])) pos = next_pos(ld->line, pos, encoding);
  320 
  321     if (pos == ld->line_len) return 0; /* Blank lines don't count. */
  322 
  323     pa_space_pos = pos;
  324     pa_space_len = calc_width(ld, pos, tab_size, encoding);
  325 
  326     if (pos == 0) {
  327         return 1;
  328     }
  329 
  330     if ((pa_space = malloc(pos))) {
  331         memcpy(pa_space, ld->line, pos);
  332         return 1;
  333     }
  334 
  335     return 0;
  336 }
  337 
  338 
  339 /* trim_trailing_space() removes spaces from the end of the line referred to by
  340    the line_desc ld. The int line is necessary for undo to work. */
  341 
  342 static void trim_trailing_space(buffer * const b, line_desc *ld, const int64_t line, const encoding_type encoding) {
  343     if (!ld->line) return;
  344     int64_t pos = ld->line_len;
  345     while (pos > 0 && isasciispace(ld->line[pos - 1])) pos = prev_pos(ld->line, pos, encoding);
  346     if (pos >= 0 && pos < ld->line_len) delete_stream(b, ld, line, pos, ld->line_len - pos);
  347 }
  348 
  349 /* is_part_of_paragraph() determines if the line ld refers to could be
  350    considered part of a paragraph based on its leading spaces compared to
  351    pa_space_len. If they are the same, is_part_of_paragraph() returns 1, and
  352    *first_non_blank is set to the position of the first non-blank character on
  353    the line. Otherwise, *first_non_blank is -1 and is_part_of_paragraph()
  354    returns 0. */
  355 
  356 static int is_part_of_paragraph(const line_desc * const ld, const int tab_size, int64_t * const first_non_blank, const encoding_type encoding) {
  357     *first_non_blank = -1;
  358     if (!ld->line) return 0;
  359 
  360     int64_t pos = 0;
  361     while (pos < ld->line_len && isasciispace(ld->line[pos])) pos = next_pos(ld->line, pos, encoding);
  362     if (pos < ld->line_len && calc_width(ld, pos, tab_size, encoding) == pa_space_len) {
  363         *first_non_blank = pos;
  364         return 1;
  365     }
  366     return 0;
  367 }
  368 
  369 /* paragraph() reformats a paragraph following the current parameters for
  370    right_margin (a value of 0 forces the use of the full screen width).  On
  371    completion the cursor is positioned either:
  372 
  373      * on the first non-blank character after the paragraph if there is one, or
  374 
  375      * on a blank line following the paragraph if there is one, or
  376 
  377      * on the last line of the paragraph.
  378 
  379    paragraph() returns OK unless the cursor ends up on the last line of the
  380    file, in which case it returns ERROR. */
  381 
  382 int paragraph(buffer * const b) {
  383     line_desc *ld = b->cur_line_desc, *start_line_desc = ld;
  384 
  385     if (!ld->line) return line_down(b);
  386 
  387     /* Establish appropriate leading space. This will be taken from the line
  388       following the current line if it is non-blank. Otherwise it will be
  389       taken from the current line. Save a copy of it for later as space[]. **/
  390 
  391     if ( !(    (ld->ld_node.next->next && save_space((line_desc *)ld->ld_node.next, b->opt.tab_size, b->encoding))
  392             || save_space(ld, b->opt.tab_size, b->encoding) )
  393        ) return line_down(b);
  394 
  395     int64_t pos = b->cur_pos;
  396     b->cur_pos = -1;
  397 
  398     start_undo_chain(b);
  399 
  400     /* This insertion and deletion of a single character ensures
  401        that the cursor ends up here after an undo. */
  402     int64_t line = b->cur_line;
  403     insert_one_char(b, ld, line, 0, ' ');
  404     delete_stream(b, ld, line, 0, 1);
  405 
  406     const int right_margin = b->opt.right_margin ? b->opt.right_margin : ne_columns;
  407     bool done;
  408 
  409     do {
  410         done = true; /* set this to false if we do any work in the loop. */
  411 
  412         trim_trailing_space(b, ld, line, b->encoding);
  413 
  414         /* Suck up subsequent lines until this one is long enough to need splitting */
  415         while ((calc_width(ld, ld->line_len, b->opt.tab_size, b->encoding) <= right_margin) &&
  416                ld->ld_node.next->next && is_part_of_paragraph((line_desc *)ld->ld_node.next, b->opt.tab_size, &pos, b->encoding)) {
  417             line_desc *ld_next = (line_desc *)ld->ld_node.next;
  418             insert_one_char(b, ld, line, ld->line_len, ' ');
  419             if (pos) delete_stream(b, ld_next, line + 1, 0, pos); /* pos was set by is_part_of_paragraph() above. */
  420             delete_stream(b, ld, line, ld->line_len, 1);          /* joins next line to this one */
  421             trim_trailing_space(b, ld, line, b->encoding);
  422             done = false;
  423         }
  424 
  425         if (calc_width(ld, ld->line_len, b->opt.tab_size, b->encoding) > right_margin) {
  426             int64_t spaces;
  427             int64_t split_pos;
  428             /* Skip past leading spaces... */
  429             pos = 0;
  430             while(pos < ld->line_len && isasciispace(ld->line[pos]))
  431                 pos = next_pos(ld->line, pos, b->encoding);
  432 
  433             /* Find the split point */
  434             split_pos = spaces = 0;
  435             while (pos < ld->line_len && (calc_width(ld, pos, b->opt.tab_size, b->encoding) < right_margin || ! split_pos)) {
  436                 if (isasciispace(ld->line[pos])) {
  437                     split_pos = pos;
  438                     spaces = 0;
  439                     while (pos < ld->line_len && isasciispace(ld->line[pos])) {
  440                         pos = next_pos(ld->line, pos, b->encoding);
  441                         spaces++;
  442                     }
  443                 }
  444                 else pos = next_pos(ld->line, pos, b->encoding);
  445             }
  446             if (split_pos) {
  447                 done = false;
  448                 /* Remove any space at the split point. */
  449                 if (spaces) delete_stream(b, ld, line, split_pos, spaces);
  450 
  451                 /* Split the line at the split point.  (We are done with this line) */
  452                 insert_one_line(b, ld, line, split_pos);
  453 
  454                 /* Make the new next line the current line **/
  455                 if (ld->ld_node.next->next) {
  456                     ld = (line_desc *)ld->ld_node.next;
  457                     line++;
  458                     if (pa_space && pa_space_len && pa_space_pos)
  459                         insert_stream(b, ld, line, 0, pa_space, pa_space_pos);
  460                     trim_trailing_space(b, ld, line, b->encoding);
  461                 }
  462             } else { /* Line not split; is there a next one in the paragraph? */
  463                 if ( ld->ld_node.next->next && is_part_of_paragraph((line_desc *)ld->ld_node.next, b->opt.tab_size, &pos, b->encoding) ) {
  464                     ld = (line_desc *)ld->ld_node.next;
  465                     line++;
  466                     done = false;
  467                 }
  468             }
  469         }
  470     } while (!stop && !done);
  471 
  472     end_undo_chain(b);
  473 
  474     if (pa_space) {
  475         free(pa_space);
  476         pa_space = NULL;
  477     }
  478 
  479     if (b->syn) {
  480         b->attr_len = -1;
  481         need_attr_update = true;
  482         update_syntax_states(b, -1, start_line_desc, (line_desc *)ld->ld_node.next);
  483     }
  484     update_window_lines(b, b->cur_line_desc, b->cur_y, ne_lines - 2, false);
  485 
  486     goto_line_pos(b, line, pos);
  487     if (stop || line_down(b) == ERROR) return stop ? STOPPED : ERROR;
  488 
  489     /* Try to find the first non-blank starting with this line. */
  490     ld = b->cur_line_desc;
  491     line = b->cur_line;
  492 
  493     do {
  494         if (ld->line) {
  495             for (pos = 0; pos < ld->line_len; pos = next_pos(ld->line, pos, b->encoding)) {
  496                 if (!isasciispace(ld->line[pos])) {
  497                     goto_line_pos(b, line, pos);
  498                     return ld->ld_node.next ? OK : ERROR;
  499                 }
  500             }
  501         }
  502         ld = (line_desc *)ld->ld_node.next;
  503         line++;
  504     } while (ld->ld_node.next);
  505 
  506     return b->cur_line_desc->ld_node.next ? OK : ERROR;
  507 }
  508 
  509 
  510 /* Centers the current line with respect to the right_margin parameter. If the
  511    line (without spaces) is longer than the right margin, nothing happens. */
  512 
  513 int center(buffer * const b) {
  514 
  515     line_desc * const ld = b->cur_line_desc;
  516     const int right_margin = b->opt.right_margin ? b->opt.right_margin : ne_columns;
  517 
  518     int64_t
  519         len,
  520         start_pos = 0,
  521         end_pos = ld->line_len;
  522 
  523     while(start_pos < ld->line_len && isasciispace(ld->line[start_pos])) start_pos = next_pos(ld->line, start_pos, b->encoding);
  524     if (start_pos == ld->line_len) return OK;
  525     while(isasciispace(ld->line[prev_pos(ld->line, end_pos, b->encoding)])) end_pos = prev_pos(ld->line, end_pos, b->encoding);
  526 
  527     len = b->encoding == ENC_UTF8 ? utf8strlen(&ld->line[start_pos], end_pos - start_pos) : end_pos - start_pos;
  528     if (len >= right_margin) return OK;
  529 
  530     b->cur_pos = -1;
  531     start_undo_chain(b);
  532 
  533     delete_stream(b, ld, b->cur_line, end_pos, ld->line_len - end_pos);
  534     delete_stream(b, ld, b->cur_line, 0, start_pos);
  535     insert_spaces(b, ld, b->cur_line, 0, (right_margin - len) / 2);
  536 
  537     end_undo_chain(b);
  538 
  539     return OK;
  540 }
  541 
  542 
  543 
  544 /* Indents a line of the amount of whitespace present on the previous line, stopping
  545    at a given column (use INT_MAX for not stopping). The number of
  546    inserted bytes is returned. */
  547 
  548 int auto_indent_line(buffer * const b, const int64_t line, line_desc * const ld, const int64_t up_to_col) {
  549 
  550     line_desc * const prev_ld = (line_desc *)ld->ld_node.prev;
  551 
  552     if (!prev_ld->ld_node.prev || prev_ld->line_len == 0) return 0;
  553     assert_line_desc(prev_ld, b->encoding);
  554 
  555     int c;
  556     int64_t pos = 0;
  557     for(int64_t col = 0; pos < prev_ld->line_len && ne_isspace(c = get_char(&prev_ld->line[pos], b->encoding), b->encoding); ) {
  558         col += (c == '\t' ? b->opt.tab_size - col % b->opt.tab_size : 1);
  559         if (col > up_to_col) break;
  560         pos = next_pos(prev_ld->line, pos, b->encoding);
  561     }
  562     if (pos) insert_stream(b, ld, line, 0, prev_ld->line, pos);
  563     return pos;
  564 }
  565 
  566 
  567 /* Shift a block of lines left or right with whitespace adjustments. */
  568 
  569 int shift(buffer * const b, char *p, char *msg, int msg_size) {
  570     const bool use_tabs = b->opt.tabs && b->opt.shift_tabs;
  571     const int64_t init_line = b->cur_line, init_pos = b->cur_pos, init_y = b->cur_y;
  572 
  573     line_desc *ld = NULL, *start_line_desc = NULL;
  574     int64_t shift_size = 1;
  575     char dir = '>';
  576     int shift_mag = b->opt.tab_size, rc = 0;
  577 
  578     /* Parse parm p; looks like [<|>] ### [s|t], but we allow them
  579        in any order, once, with optional white space. */
  580     if (p) {
  581         int dir_b = 0, size_b = 0, st_b = 0;
  582         while (*p) {
  583             if (isasciispace(*p)) p++;
  584             else if (!dir_b && (dir_b = (*p == '<' || *p == '>'))) dir = *p++;
  585             else if (!size_b && (size_b = isdigit((unsigned char)*p))) {
  586                 errno = 0;
  587                 shift_size = strtoll(p, &p, 10);
  588                 if (errno) return INVALID_SHIFT_SPECIFIED;
  589             } else if (!st_b && (st_b = (*p == 's' || *p == 'S'))) {
  590                 shift_mag = 1;
  591                 p++;
  592             } else if (!st_b && (st_b = (*p == 't' || *p == 'T'))) p++;
  593             else return INVALID_SHIFT_SPECIFIED;
  594         }
  595     }
  596     shift_size *= max(1, shift_mag);
  597     if (shift_size == 0) return INVALID_SHIFT_SPECIFIED;
  598 
  599     int64_t first_line = b->cur_line, last_line = b->cur_line, left_col = 0;
  600 
  601     if (b->marking) {
  602         if (b->mark_is_vertical) left_col = min(calc_width(b->cur_line_desc, b->block_start_pos, b->opt.tab_size, b->encoding),
  603                                                 calc_width(b->cur_line_desc, b->cur_pos,         b->opt.tab_size, b->encoding));
  604         first_line = min(b->block_start_line, b->cur_line);
  605         last_line  = max(b->block_start_line, b->cur_line);
  606     }
  607 
  608     /* If we're shifting left (dir=='<'), verify that we have sufficient white space
  609        to remove on all the relevant lines before making any changes, i. */
  610 
  611     if (dir == '<') {
  612         shift_size = -shift_size; /* signed shift_size now also indicates direction. */
  613         for (int64_t line = first_line; !rc && line <= last_line; line++) {
  614             int64_t pos;
  615             goto_line(b, line);
  616             pos = calc_pos(b->cur_line_desc, left_col, b->opt.tab_size, b->encoding);
  617             while (pos < b->cur_line_desc->line_len &&
  618                    left_col - calc_width(b->cur_line_desc, pos, b->opt.tab_size, b->encoding) > shift_size) {
  619                 if (isasciispace(b->cur_line_desc->line[pos]))
  620                     pos = next_pos(b->cur_line_desc->line, pos, b->encoding);
  621                 else {
  622                     rc = INSUFFICIENT_WHITESPACE;
  623                     break;
  624                 }
  625             }
  626         }
  627     }
  628 
  629 
  630     if (!rc) {
  631         start_undo_chain(b);
  632         for (int64_t line = first_line; line <= last_line; line++) {
  633             int64_t pos, c_pos, c_col_orig, offset;
  634             b->attr_len = -1;
  635             goto_line(b, line);
  636             b->cur_pos = -1;
  637             ld = b->cur_line_desc;
  638             if (line == first_line) start_line_desc = ld;
  639             pos = calc_pos(ld, left_col, b->opt.tab_size, b->encoding);
  640             /* If left_col is in the middle of a tab, pos will be on that tab. */
  641             /* whitespace adjustment strategy:
  642                1. Starting from left_col, advance to the right to the first non-blank character C.
  643                2. Note C's col. The desired new column is this value +/- shift_size.
  644                3. Move left looking for the first tab or non-whitespace or the left_col, whichever comes first.
  645                   Whitespace changes all take place at that transition point.
  646                4. While C's col is wrong
  647                     if C's col is too far to the right,
  648                       if we're on a space, delete it;
  649                       else if there's a tab to our left, delete it;
  650                       else we should not have started, because it's not possible!
  651                     if C's col is too far to the left,
  652                        if its needs to be beyond the next tab stop,
  653                          insert a tab and move right;
  654                        else insert a space. */
  655             /* 1. */
  656             while (pos < ld->line_len && isasciispace(ld->line[pos]))
  657                 pos = next_pos(ld->line, pos, b->encoding);
  658             if (pos >= ld->line_len) continue; /* We ran off the end of the line. */
  659             /* line[pos] should be the first non-blank character. */
  660             /* 2. */
  661             c_pos = pos;
  662             c_col_orig = calc_width(ld, c_pos, b->opt.tab_size, b->encoding);
  663             /* 3. */
  664             while (pos && ld->line[pos-1] == ' ')
  665                 pos = prev_pos(ld->line, pos, b->encoding);
  666             /* If pos is non-zero, it should be on a blank, with only blanks between here and c_pos. */
  667             /* 4. */
  668             /* offset = how_far_we_have_moved - how_far_we_want_to_move. */
  669             while (!stop && (offset = calc_width(ld, c_pos, b->opt.tab_size, b->encoding)-c_col_orig - shift_size)) {
  670                 if (offset > 0) { /* still too far right; remove whitespace */
  671                     if (ld->line[pos] == ' ') {
  672                         delete_stream(b, ld, b->cur_line, pos, 1);
  673                         c_pos--;
  674                     }
  675                     else if (pos) { /* should be a tab just to our left */
  676                         pos = prev_pos(ld->line, pos, b->encoding); /* now we're on the tab */
  677                         if (ld->line[pos] == '\t') {
  678                             delete_stream(b, ld, b->cur_line, pos, 1);
  679                             c_pos--;
  680                         }
  681                         else break; /* Should have been a tab. This should never happen! Give up on this line and go mangle the next one. */
  682                     }
  683                     else break; /* This should never happen; give up on this line and go mangle the next one. */
  684                 }
  685                 else if (offset < 0) { /* too far left; insert whitespace */
  686                     char c = ' ';
  687                     if (use_tabs && (b->opt.tab_size - calc_width(ld, pos, b->opt.tab_size, b->encoding) % b->opt.tab_size) <= -offset )
  688                         c = '\t';
  689                     if (insert_one_char(b, ld, b->cur_line, pos, c)) {
  690                         break;
  691                     }
  692                     pos++;
  693                     c_pos++;
  694                 }
  695             }
  696         }
  697         end_undo_chain(b);
  698         if (b->syn) {
  699             b->attr_len = -1;
  700             need_attr_update = true;
  701             update_syntax_states(b, -1, start_line_desc, (line_desc *)ld->ld_node.next);
  702         }
  703         update_window_lines(b, b->top_line_desc, 0, ne_lines - 2, false);
  704     }
  705 
  706     /* put the screen back where way we found it. */
  707     goto_line_pos(b, init_line, init_pos);
  708     delay_update();
  709     const int64_t avshift = b->cur_y - init_y;
  710     if (avshift) {
  711         snprintf(msg, msg_size, "%c%" PRId64, avshift > 0 ? 'T' :'B', avshift > 0 ? avshift : -avshift);
  712         adjust_view(b, msg);
  713     }
  714 
  715     return rc;
  716 }
  717