Ticket #3250: mc-3250-viewer-rewrite-v3.patch

File mc-3250-viewer-rewrite-v3.patch, 71.0 KB (added by egmont, 4 years ago)

Reimplementation, v3

  • AUTHORS

    From 67f69102c845418e59fbf302b7c0df86978154ef Mon Sep 17 00:00:00 2001
    From: Egmont Koblinger <egmont@gmail.com>
    Date: Sun, 14 Sep 2014 00:42:46 +0200
    Subject: [PATCH] 3250-v3
    
    ---
     AUTHORS                         |   1 +
     src/viewer/Makefile.am          |   2 +-
     src/viewer/actions_cmd.c        |   3 +
     src/viewer/ascii.c              | 989 ++++++++++++++++++++++++++++++++++++++++
     src/viewer/datasource.c         |   8 +-
     src/viewer/display.c            |   4 -
     src/viewer/internal.h           |  32 +-
     src/viewer/lib.c                |  13 +-
     src/viewer/mcviewer.c           |   7 +
     src/viewer/move.c               | 114 ++---
     src/viewer/nroff.c              | 158 +------
     src/viewer/plain.c              | 204 ---------
     tests/src/viewer/viewertest.txt | Bin 0 -> 4680 bytes
     13 files changed, 1071 insertions(+), 464 deletions(-)
     create mode 100644 src/viewer/ascii.c
     delete mode 100644 src/viewer/plain.c
     create mode 100644 tests/src/viewer/viewertest.txt
    
    diff --git a/AUTHORS b/AUTHORS
    index bb85c83..60ef7f7 100644
    a b Egmont Koblinger <egmont@gmail.com> 
    6464        Support of extended mouse clicks beyond 223 column 
    6565        Support of bracketed paste mode of xterm 
    6666                (http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Bracketed%20Paste%20Mode) 
     67        Rewritten viewer 
    6768 
    6869Erwin van Eijk <wabbit@corner.iaf.nl> 
    6970 
  • src/viewer/Makefile.am

    diff --git a/src/viewer/Makefile.am b/src/viewer/Makefile.am
    index 53bc7a4..0602084 100644
    a b noinst_LTLIBRARIES = libmcviewer.la 
    33 
    44libmcviewer_la_SOURCES = \ 
    55        actions_cmd.c \ 
     6        ascii.c \ 
    67        coord_cache.c \ 
    78        datasource.c \ 
    89        dialogs.c \ 
    libmcviewer_la_SOURCES = \ 
    1617        mcviewer.h \ 
    1718        move.c \ 
    1819        nroff.c \ 
    19         plain.c \ 
    2020        search.c 
    2121 
    2222AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS) $(PCRE_CPPFLAGS) 
  • src/viewer/actions_cmd.c

    diff --git a/src/viewer/actions_cmd.c b/src/viewer/actions_cmd.c
    index 8df149e..6b69e66 100644
    a b mcview_execute_cmd (mcview_t * view, unsigned long command) 
    510510        break; 
    511511    case CK_Bookmark: 
    512512        view->dpy_start = view->marks[view->marker]; 
     513        view->dpy_paragraph_skip_lines = 0;     /* TODO: remember this value in the marker? */ 
     514        view->dpy_wrap_dirty = TRUE; 
    513515        view->dirty++; 
    514516        break; 
    515517#ifdef HAVE_CHARSET 
    mcview_adjust_size (WDialog * h) 
    592594    widget_set_size (WIDGET (view), 0, 0, LINES - 1, COLS); 
    593595    widget_set_size (WIDGET (b), LINES - 1, 0, 1, COLS); 
    594596 
     597    view->dpy_wrap_dirty = TRUE; 
    595598    mcview_compute_areas (view); 
    596599    mcview_update_bytes_per_line (view); 
    597600} 
  • new file src/viewer/ascii.c

    diff --git a/src/viewer/ascii.c b/src/viewer/ascii.c
    new file mode 100644
    index 0000000..a011aa1
    - +  
     1/* 
     2   Internal file viewer for the Midnight Commander 
     3   Function for plain view 
     4 
     5   Copyright (C) 1994-2014 
     6   Free Software Foundation, Inc. 
     7 
     8   Written by: 
     9   Miguel de Icaza, 1994, 1995, 1998 
     10   Janne Kukonlehto, 1994, 1995 
     11   Jakub Jelinek, 1995 
     12   Joseph M. Hinkle, 1996 
     13   Norbert Warmuth, 1997 
     14   Pavel Machek, 1998 
     15   Roland Illig <roland.illig@gmx.de>, 2004, 2005 
     16   Slava Zanko <slavazanko@google.com>, 2009 
     17   Andrew Borodin <aborodin@vmail.ru>, 2009-2014 
     18   Ilia Maslakov <il.smind@gmail.com>, 2009 
     19   Rewritten almost from scratch by: 
     20   Egmont Koblinger <egmont@gmail.com>, 2014 
     21 
     22   This file is part of the Midnight Commander. 
     23 
     24   The Midnight Commander is free software: you can redistribute it 
     25   and/or modify it under the terms of the GNU General Public License as 
     26   published by the Free Software Foundation, either version 3 of the License, 
     27   or (at your option) any later version. 
     28 
     29   The Midnight Commander is distributed in the hope that it will be useful, 
     30   but WITHOUT ANY WARRANTY; without even the implied warranty of 
     31   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     32   GNU General Public License for more details. 
     33 
     34   You should have received a copy of the GNU General Public License 
     35   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
     36 
     37   ------------------------------------------------------------------------------------------------ 
     38 
     39   The viewer is implemented along the following design principles: 
     40 
     41   Goals: Always display simple scripts, double wide (CJK), combining accents and spacing marks 
     42   (often used e.g. in Devanagari) perfectly. Make the arrow keys always work correctly. 
     43 
     44   Absolutely non-goal: RTL. 
     45 
     46   Terminology: 
     47 
     48   - A "paragraph" is the text between two adjacent newline characters. A "line" or "row" is a 
     49   visual row on the screen. In wrap mode, the viewer formats a paragraph into one or more lines. 
     50 
     51   - The Unicode glossary <http://www.unicode.org/glossary/> doesn't seem to have a notion of "base 
     52   character followed by zero or more combining characters". The closest matches are "Combining 
     53   Character Sequence" meaning a base character followed by one or more combining characters, or 
     54   "Grapheme" which seems to exclude non-printable characters such as newline. In this file, 
     55   "combining character sequence" (or any obvious abbreviation thereof) means a base character 
     56   followed by zero or more (up to a current limit of 4) combining characters. 
     57 
     58   ------------------------------------------------------------------------------------------------ 
     59 
     60   The parser-formatter is designed to be stateless across paragraphs. This is so that we can walk 
     61   backwards without having to reparse the whole file (although we still need to reparse and 
     62   reformat the whole paragraph, but it's a lot better). This principle needs to be changed if we 
     63   ever get to address tickets 1849/2977, but then we can still store (for efficiency) the parser 
     64   state at the beginning of the paragraph, and safely walk backwards if we don't cross an escape 
     65   character. 
     66 
     67   The parser-formatter, however, definitely needs to carry a state across lines. Currently this 
     68   state contains: 
     69 
     70   - The logical column (as if we didn't wrap). This is used for handling TAB characters after a 
     71   wordwrap consistently with less. 
     72 
     73   - Whether the last nroff character was bold or underlined. This is used for displaying the 
     74   ambiguous _\b_ sequence consistently with less. 
     75 
     76   - Whether the desired way of displaying a lonely combining accent or spacing mark is to place it 
     77   over a dotted circle (we do this at the beginning of the paragraph of after a TAB), or to ignore 
     78   the combining char and show replacement char for the spacing mark (we do this if e.g. too many 
     79   of these were encountered and hence we don't glue them with their base character). 
     80 
     81   - (This state needs to be expanded if e.g. we decide to print verbose replacement characters 
     82   (e.g. "<U+0080>") and allow these to wrap around lines.) 
     83 
     84   The state also contains the file offset, as it doesn't make sense to ever know the state without 
     85   knowing the corresponding offset. 
     86 
     87   The state depends on various settings (viewer width, encoding, nroff mode, charwrap or wordwrap 
     88   mode (if we'll have that one day) etc.), needs to be recomputed if any of these changes. 
     89 
     90   Walking forwards is usually relatively easy both in the file and on the screen. Walking 
     91   backwards within a paragraph would only be possible in some special cases and even then it would 
     92   be painful, so we always walk back to the beginning of the paragraph and reparse-reformat from 
     93   there. 
     94 
     95   (Walking back within a line in the file would have at least the following difficulties: handling 
     96   the parser state; processing invalid UTF-8; processing invalid nroff (e.g. what is "_\bA\bA"?). 
     97   Walking back on the display: we wouldn't know where to display the last line of a paragraph, or 
     98   where to display a line if its following line starts with a wide (CJK or Tab) character. Long 
     99   story short: just forget this approach.) 
     100 
     101   Most important variables: 
     102 
     103   - dpy_start: Both in unwrap and wrap modes this points to the beginning of the topmost displayed 
     104   paragraph. 
     105 
     106   - dpy_text_column: Only in unwrap mode, an additional horizontal scroll. 
     107 
     108   - dpy_paragraph_skip_lines: Only in wrap mode, an additional vertical scroll (the number of 
     109   lines that are scrolled off at the top from the topmost paragraph). 
     110 
     111   - dpy_state_top: Only in wrap mode, the offset and parser-formatter state at the line where 
     112   displaying the file begins is cached here. 
     113 
     114   - dpy_wrap_dirty: If some parameter has changed that makes it necessary to reparse-redisplay the 
     115   topmost paragraph. 
     116 
     117   In wrap mode, the three variables "dpy_start", "dpy_paragraph_skip_lines" and "dpy_state_top" 
     118   are kept consistent. Think of the first two as the ones describing the position, and the third 
     119   as a cached value for better performance so that we don't need to wrap the invisible beginning 
     120   of the topmost paragraph over and over again. The third value needs to be recomputed each time a 
     121   parameter that influences parsing or displaying the file (e.g. width of screen, encoding, nroff 
     122   mode) changes, this is signaled by "dpy_wrap_dirty" to force recomputing "dpy_state_top" (and 
     123   clamp "dpy_paragraph_skip_lines" if necessary). 
     124 
     125   ------------------------------------------------------------------------------------------------ 
     126 
     127   Help integration 
     128 
     129   I'm planning to port the help viewer to this codebase. 
     130 
     131   Splitting at sections would still happen in the help viewer. It would either copy a section, or 
     132   set force_max and a similar force_min to limit displaying to one section only. 
     133 
     134   Parsing the help format would go next to the nroff parser. The colors, alternate character set, 
     135   and emitting the version number would go to the "state". (The version number would be 
     136   implemented by emitting remaining characters of a buffer in the "state" one by one, without 
     137   advancing in the file position.) 
     138 
     139   The active link would be drawn similarly to the search highlight. Other than that, the viewer 
     140   wouldn't care about links (except for their color). help.c would keep track of which one is 
     141   highlighted, how to advance to the next/prev on an arrow, how the scroll offset needs to be 
     142   adjusted when moving, etc. 
     143 
     144   Add wrapping at word boundaries to where wrapping at char boundaries happens now. 
     145 */ 
     146 
     147#include <config.h> 
     148 
     149#include "lib/global.h" 
     150#include "lib/tty/tty.h" 
     151#include "lib/skin.h" 
     152#include "lib/util.h"           /* is_printable() */ 
     153#ifdef HAVE_CHARSET 
     154#include "lib/charsets.h" 
     155#endif 
     156 
     157#include "src/setup.h"          /* option_tab_spacing */ 
     158 
     159#include "internal.h" 
     160 
     161/*** global variables ****************************************************************************/ 
     162 
     163/*** file scope macro definitions ****************************************************************/ 
     164 
     165/* The Unicode standard recommends that lonely combining characters are printed over a dotted 
     166 * circle. If the terminal is not UTF-8, this will be replaced by a dot anyway. */ 
     167#define BASE_CHARACTER_FOR_LONELY_COMBINING 0x25CC      /* dotted circle */ 
     168#define MAX_COMBINING_CHARS 4   /* both slang and ncurses support exactly 4 */ 
     169 
     170/* I think anything other than space (e.g. arrows) just introduce visual clutter without actually 
     171 * adding value. */ 
     172#define PARTIAL_CJK_AT_LEFT_MARGIN  ' ' 
     173#define PARTIAL_CJK_AT_RIGHT_MARGIN ' ' 
     174 
     175/* 
     176 * Wrap mode: This is for safety so that jumping to the end of file (which already includes 
     177 * scrolling back by a page) and then walking backwards is reasonably fast, even if the file is 
     178 * extremely large and consists of maybe full zeros or something like that. If there's no newline 
     179 * found within this limit, just start displaying from there and see what happens. We might get 
     180 * some displaying parameteres (most importantly the columns) incorrect, but at least will show the 
     181 * file without spinning the CPU for ages. When scrolling back to that point, the user might see a 
     182 * garbled first line (even starting with an invalid partial UTF-8), but then walking back by yet 
     183 * another line should fix it. 
     184 * 
     185 * Unwrap mode: This is not used, we wouldn't be able to do anything reasonable without walking 
     186 * back a whole paragraph (well, view->data_area.height paragraphs actually). 
     187 */ 
     188#define MAX_BACKWARDS_WALK_IN_PARAGRAPH (100 * 1000) 
     189 
     190/*** file scope type declarations ****************************************************************/ 
     191 
     192/*** file scope variables ************************************************************************/ 
     193 
     194/*** file scope functions ************************************************************************/ 
     195 
     196/* TODO: These methods shouldn't be necessary, see ticket 3257 */ 
     197 
     198static int 
     199mcview_wcwidth (const mcview_t * view, int c) 
     200{ 
     201#ifdef HAVE_CHARSET 
     202    if (view->utf8) 
     203    { 
     204        if (g_unichar_iswide (c)) 
     205            return 2; 
     206        if (g_unichar_iszerowidth (c)) 
     207            return 0; 
     208    } 
     209#endif /* HAVE_CHARSET */ 
     210    return 1; 
     211} 
     212 
     213static gboolean 
     214mcview_ismark (const mcview_t * view, int c) 
     215{ 
     216#ifdef HAVE_CHARSET 
     217    if (view->utf8) 
     218        return g_unichar_ismark (c); 
     219#endif /* HAVE_CHARSET */ 
     220    return FALSE; 
     221} 
     222 
     223/* actually is_non_spacing_mark_or_enclosing_mark */ 
     224static gboolean 
     225mcview_is_non_spacing_mark (const mcview_t * view, int c) 
     226{ 
     227#ifdef HAVE_CHARSET 
     228    if (view->utf8) 
     229    { 
     230        GUnicodeType type = g_unichar_type (c); 
     231        return type == G_UNICODE_NON_SPACING_MARK || type == G_UNICODE_ENCLOSING_MARK; 
     232    } 
     233#endif /* HAVE_CHARSET */ 
     234    return FALSE; 
     235} 
     236 
     237#if 0 
     238static gboolean 
     239mcview_is_spacing_mark (const mcview_t * view, int c) 
     240{ 
     241#ifdef HAVE_CHARSET 
     242    if (view->utf8) 
     243    { 
     244        return g_unichar_type (c) == G_UNICODE_SPACING_MARK; 
     245    } 
     246#endif /* HAVE_CHARSET */ 
     247    return FALSE; 
     248} 
     249#endif /* 0 */ 
     250 
     251static gboolean 
     252mcview_isprint (const mcview_t * view, int c) 
     253{ 
     254#ifdef HAVE_CHARSET 
     255    if (!view->utf8) 
     256        c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter); 
     257    return g_unichar_isprint (c); 
     258#endif /* HAVE_CHARSET */ 
     259    /* TODO this is very-very buggy by design: ticket 3257 comments 0-1 */ 
     260    return is_printable (c); 
     261} 
     262 
     263static int 
     264mcview_char_display (const mcview_t * view, int c, char *s) 
     265{ 
     266#ifdef HAVE_CHARSET 
     267    if (mc_global.utf8_display) 
     268    { 
     269        if (!view->utf8) 
     270            c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter); 
     271        if (!g_unichar_isprint (c)) 
     272            c = '.'; 
     273        return g_unichar_to_utf8 (c, s); 
     274    } 
     275    else if (view->utf8) 
     276    { 
     277        if (g_unichar_iswide (c)) 
     278        { 
     279            s[0] = s[1] = '.'; 
     280            return 2; 
     281        } 
     282        if (g_unichar_iszerowidth (c)) 
     283            return 0; 
     284        /* TODO the is_printable check below will be broken for this */ 
     285        c = convert_from_utf_to_current_c (c, view->converter); 
     286    } 
     287    else 
     288    { 
     289        /* TODO the is_printable check below will be broken for this */ 
     290        c = convert_to_display_c (c); 
     291    } 
     292#endif /* HAVE_CHARSET */ 
     293    /* TODO this is very-very buggy by design: ticket 3257 comments 0-1 */ 
     294    if (!is_printable (c)) 
     295        c = '.'; 
     296    *s = c; 
     297    return 1; 
     298} 
     299 
     300/* --------------------------------------------------------------------------------------------- */ 
     301 
     302/* 
     303 * Just for convenience, a common interface in front of mcview_get_utf and mcview_get_byte, so that 
     304 * the caller doesn't have to care about utf8 vs 8-bit modes. 
     305 * 
     306 * Normally: stores c, updates state, returns TRUE. 
     307 * At EOF: state is unchanged, c is undefined, returns FALSE. 
     308 * 
     309 * Also, temporary hack: handle force_max here. 
     310 * TODO: move it to lower layers (datasource.c)? 
     311 */ 
     312static gboolean 
     313mcview_get_next_char (mcview_t * view, mcview_state_machine_t * state, int *c) 
     314{ 
     315    gboolean result; 
     316    int bytes_consumed; 
     317 
     318    /* Pretend EOF if we reached force_max */ 
     319    if (view->force_max >= 0 && state->offset >= view->force_max) 
     320    { 
     321        return FALSE; 
     322    } 
     323#ifdef HAVE_CHARSET 
     324    if (view->utf8) 
     325    { 
     326        *c = mcview_get_utf (view, state->offset, &bytes_consumed, &result); 
     327        if (!result) 
     328            return FALSE; 
     329        /* Pretend EOF if we crossed force_max */ 
     330        if (view->force_max >= 0 && state->offset + bytes_consumed > view->force_max) 
     331        { 
     332            return FALSE; 
     333        } 
     334        state->offset += bytes_consumed; 
     335        return TRUE; 
     336    } 
     337#endif /* HAVE_CHARSET */ 
     338    if (!mcview_get_byte (view, state->offset, c)) 
     339        return FALSE; 
     340    state->offset++; 
     341    return TRUE; 
     342} 
     343 
     344/* 
     345 * This function parses the next nroff character and gives it to you along with its desired color, 
     346 * so you never have to care about nroff again. 
     347 * 
     348 * The nroff mode does the backspace trick for every single character (Unicode codepoint). At least 
     349 * that's what the GNU groff 1.22 package produces, and that's what less 458 expects. For 
     350 * double-wide characters (CJK), still only a single backspace is emitted. For combining accents 
     351 * and such, the print-backspace-print step is repeated for the base character and then for each 
     352 * accent separately. 
     353 * 
     354 * So, the right place for this layer is after the bytes are interpreted in UTF-8, but before 
     355 * joining a base character with its combining accents. 
     356 * 
     357 * Normally: stores c and color, updates state, returns TRUE. 
     358 * At EOF: state is unchanged, c and color are undefined, returns FALSE. 
     359 * 
     360 * color can be null if the caller doesn't care. 
     361 */ 
     362static gboolean 
     363mcview_get_next_maybe_nroff_char (mcview_t * view, mcview_state_machine_t * state, int *c, 
     364                                  int *color) 
     365{ 
     366    mcview_state_machine_t state_after_nroff; 
     367    int c2, c3; 
     368 
     369    if (color != NULL) 
     370        *color = VIEW_NORMAL_COLOR; 
     371 
     372    if (!view->text_nroff_mode) 
     373        return mcview_get_next_char (view, state, c); 
     374 
     375    if (!mcview_get_next_char (view, state, c)) 
     376        return FALSE; 
     377    /* Don't allow nroff formatting around CR, LF, TAB or other special chars */ 
     378    if (!mcview_isprint (view, *c)) 
     379        return TRUE; 
     380 
     381    state_after_nroff = *state; 
     382 
     383    if (!mcview_get_next_char (view, &state_after_nroff, &c2)) 
     384        return TRUE; 
     385    if (c2 != '\b') 
     386        return TRUE; 
     387 
     388    if (!mcview_get_next_char (view, &state_after_nroff, &c3)) 
     389        return TRUE; 
     390    if (!mcview_isprint (view, c3)) 
     391        return TRUE; 
     392 
     393    if (*c == '_' && c3 == '_') 
     394    { 
     395        *state = state_after_nroff; 
     396        if (color != NULL) 
     397            *color = 
     398                state->nroff_underscore_is_underlined ? VIEW_UNDERLINED_COLOR : VIEW_BOLD_COLOR; 
     399        return TRUE; 
     400    } 
     401    else if (*c == c3) 
     402    { 
     403        *state = state_after_nroff; 
     404        state->nroff_underscore_is_underlined = FALSE; 
     405        if (color != NULL) 
     406            *color = VIEW_BOLD_COLOR; 
     407        return TRUE; 
     408    } 
     409    else if (*c == '_') 
     410    { 
     411        *c = c3; 
     412        *state = state_after_nroff; 
     413        state->nroff_underscore_is_underlined = TRUE; 
     414        if (color != NULL) 
     415            *color = VIEW_UNDERLINED_COLOR; 
     416        return TRUE; 
     417    } 
     418    else 
     419    { 
     420        return TRUE; 
     421    } 
     422} 
     423 
     424/* 
     425 * Get one base character, along with its combining or spacing mark characters. 
     426 * 
     427 * (A spacing mark is a character that extends the base character's width 1 into a combined 
     428 * character of width 2, yet these two character cells should not be separated. E.g. Devanagari 
     429 * <U+0939><U+094B>.) 
     430 * 
     431 * This method exists mainly for two reasons. One is to be able to tell if we fit on the current 
     432 * line or need to wrap to the next one. The other is that both slang and ncurses seem to require 
     433 * that the character and its combining marks are printed in a single call (or is it just a 
     434 * limitation of mc's wrapper to them?). 
     435 * 
     436 * For convenience, this method takes care of converting CR or CR+LF into LF. 
     437 * TODO this should probably happen later, when displaying the file? 
     438 * 
     439 * Normally: stores cs and color, updates state, returns >= 1 (entries in cs). 
     440 * At EOF: state is unchanged, cs and color are undefined, returns 0. 
     441 * 
     442 * @param view ... 
     443 * @param state the parser-formatter state machine's state, updated 
     444 * @param cs store the characters here 
     445 * @param clen the room available in cs (that is, at most clen-1 combining marks are allowed), must 
     446 *   be at least 2 
     447 * @param color if non-NULL, store the color here, taken from the first codepoint's color 
     448 * @return the number of entries placed in cs, or 0 on EOF 
     449 */ 
     450static int 
     451mcview_next_combining_char_sequence (mcview_t * view, mcview_state_machine_t * state, int *cs, 
     452                                     int clen, int *color) 
     453{ 
     454    int i = 1; 
     455    mcview_state_machine_t state_after_combining; 
     456 
     457    if (!mcview_get_next_maybe_nroff_char (view, state, cs, color)) 
     458        return 0; 
     459 
     460    /* Process \r and \r\n newlines. */ 
     461    if (cs[0] == '\r') 
     462    { 
     463        int cnext; 
     464        mcview_state_machine_t state_after_crlf = *state; 
     465        if (mcview_get_next_maybe_nroff_char (view, &state_after_crlf, &cnext, NULL) 
     466            && cnext == '\n') 
     467            *state = state_after_crlf; 
     468        cs[0] = '\n'; 
     469        return 1; 
     470    } 
     471 
     472    /* We don't want combining over non-printable characters. This includes '\n' and '\t' too. */ 
     473    if (!mcview_isprint (view, cs[0])) 
     474        return 1; 
     475 
     476    if (mcview_ismark (view, cs[0])) 
     477    { 
     478        if (!state->print_lonely_combining) 
     479        { 
     480            /* First character is combining. Either just return it, ... */ 
     481            return 1; 
     482        } 
     483        else 
     484        { 
     485            /* or place this (and subsequent combining ones) over a dotted circle. */ 
     486            cs[1] = cs[0]; 
     487            cs[0] = BASE_CHARACTER_FOR_LONELY_COMBINING; 
     488            i = 2; 
     489        } 
     490    } 
     491 
     492    if (mcview_wcwidth (view, cs[0]) == 2) 
     493    { 
     494        /* Don't allow combining or spacing mark for wide characters, is this okay? */ 
     495        return 1; 
     496    } 
     497 
     498    /* Look for more combining chars. Either at most clen-1 zero-width combining chars, 
     499     * or at most 1 spacing mark. Is this logic correct? */ 
     500    for (; i < clen; i++) 
     501    { 
     502        state_after_combining = *state; 
     503        if (!mcview_get_next_maybe_nroff_char (view, &state_after_combining, &cs[i], NULL)) 
     504            return i; 
     505        if (!mcview_ismark (view, cs[i]) || !mcview_isprint (view, cs[i])) 
     506            return i; 
     507        if (g_unichar_type (cs[i]) == G_UNICODE_SPACING_MARK) 
     508        { 
     509            /* Only allow as the first combining char. Stop processing in either case. */ 
     510            if (i == 1) 
     511            { 
     512                *state = state_after_combining; 
     513                i++; 
     514            } 
     515            return i; 
     516        } 
     517        *state = state_after_combining; 
     518    } 
     519    return i; 
     520} 
     521 
     522/* 
     523 * Parse, format and possibly display one visual line of text. 
     524 * 
     525 * Formatting starts at the given "state" (which encodes the file offset and parser and formatter's 
     526 * internal state). In unwrap mode, this should point to the beginning of the paragraph with the 
     527 * default state, the additional horizontal scrolling is added here. In wrap mode, this should 
     528 * point to the beginning of the line, with the proper state at that point. 
     529 * 
     530 * In wrap mode, if a line ends in a newline, it is consumed, even if it's exactly at the right 
     531 * edge. In unwrap mode, the whole remaining line, including the newline is consumed. Displaying 
     532 * the next line should start at "state"'s new value, or if we displayed the bottom line then 
     533 * state->offset tells the file offset to be shown in the top bar. 
     534 * 
     535 * If "row" is offscreen, don't actually display the line but still update "state" and return the 
     536 * proper value. This is used by mcview_wrap_move_down to advance in the file. 
     537 * 
     538 * @param view ... 
     539 * @param state the parser-formatter state machine's state, updated 
     540 * @param row print to this row 
     541 * @param paragraph_ended store TRUE if paragraph ended by newline or EOF, FALSE if wraps to next 
     542 *   line 
     543 * @return the number of rows, that is, 0 if we were already at EOF, otherwise 1 
     544 */ 
     545static int 
     546mcview_display_line (mcview_t * view, mcview_state_machine_t * state, int row, 
     547                     gboolean * paragraph_ended) 
     548{ 
     549    const screen_dimen left = view->data_area.left; 
     550    const screen_dimen top = view->data_area.top; 
     551    const screen_dimen width = view->data_area.width; 
     552    const screen_dimen height = view->data_area.height; 
     553    off_t dpy_text_column = view->text_wrap_mode ? 0 : view->dpy_text_column; 
     554    screen_dimen col = 0; 
     555    int color; 
     556    int cs[1 + MAX_COMBINING_CHARS]; 
     557    int n; 
     558    char str[(1 + MAX_COMBINING_CHARS) * UTF8_CHAR_LEN + 1]; 
     559    int charwidth; 
     560    int i, j; 
     561    mcview_state_machine_t state_saved; 
     562 
     563    if (paragraph_ended != NULL) 
     564        *paragraph_ended = TRUE; 
     565 
     566    if (!view->text_wrap_mode && (row < 0 || row >= (int) height)) 
     567    { 
     568        /* Optimization: Fast forward to the end of the line, rather than carefully 
     569         * parsing and then not actually displaying it. */ 
     570        off_t eol = mcview_eol (view, state->offset, mcview_get_filesize (view)); 
     571        int retval = (eol > state->offset) ? 1 : 0; 
     572        mcview_state_machine_init (state, eol); 
     573        return retval; 
     574    } 
     575 
     576    while (1) 
     577    { 
     578        state_saved = *state; 
     579        n = mcview_next_combining_char_sequence (view, state, cs, 1 + MAX_COMBINING_CHARS, &color); 
     580        if (n == 0) 
     581            return (col > 0) ? 1 : 0; 
     582 
     583        if (view->search_start <= state->offset && state->offset < view->search_end) 
     584            color = SELECTED_COLOR; 
     585 
     586        if (cs[0] == '\n') 
     587        { 
     588            /* New line: reset all formatting state for the next paragraph. */ 
     589            mcview_state_machine_init (state, state->offset); 
     590            return 1; 
     591        } 
     592 
     593        if (mcview_is_non_spacing_mark (view, cs[0])) 
     594        { 
     595            /* Lonely combining character. Probably leftover after too many combining chars. Just ignore. */ 
     596            continue; 
     597        } 
     598 
     599        /* Nonprintable, or lonely spacing mark */ 
     600        if ((!mcview_isprint (view, cs[0]) || mcview_ismark (view, cs[0])) && cs[0] != '\t') 
     601            cs[0] = '.'; 
     602 
     603        charwidth = 0; 
     604        for (i = 0; i < n; i++) 
     605            charwidth += mcview_wcwidth (view, cs[i]); 
     606 
     607        /* Adjust the width for TAB. It's handled below along with the normal characters, 
     608         * so that it's wrapped consistently with them, and is painted with the proper 
     609         * attributes (although currently it can't have a special color). */ 
     610        if (cs[0] == '\t') 
     611        { 
     612            charwidth = option_tab_spacing - state->unwrapped_column % option_tab_spacing; 
     613            state->print_lonely_combining = TRUE; 
     614        } 
     615        else 
     616        { 
     617            state->print_lonely_combining = FALSE; 
     618        } 
     619 
     620        /* In wrap mode only: We're done with this row if the character sequence wouldn't fit. 
     621         * Except if at the first column, because then it wouldn't fit in the next row either. 
     622         * In this extreme case let the unwrapped code below do its best to display it. */ 
     623        if (view->text_wrap_mode && (off_t) col + charwidth > dpy_text_column + width && col > 0) 
     624        { 
     625            *state = state_saved; 
     626            if (paragraph_ended != NULL) 
     627                *paragraph_ended = FALSE; 
     628            return 1; 
     629        } 
     630 
     631        /* Display, unless outside of the viewport. */ 
     632        if (row >= 0 && row < (int) height) 
     633        { 
     634            if ((off_t) col >= dpy_text_column && 
     635                (off_t) col + charwidth <= dpy_text_column + width) 
     636            { 
     637                /* The combining character sequence fits entirely in the viewport. Print it. */ 
     638                tty_setcolor (color); 
     639                widget_move (view, top + row, left + ((off_t) col - dpy_text_column)); 
     640                if (cs[0] == '\t') 
     641                { 
     642                    for (i = 0; i < charwidth; i++) 
     643                        tty_print_char (' '); 
     644                } 
     645                else 
     646                { 
     647                    j = 0; 
     648                    for (i = 0; i < n; i++) 
     649                    { 
     650                        j += mcview_char_display (view, cs[i], str + j); 
     651                    } 
     652                    str[j] = '\0'; 
     653                    /* This is probably a bug in our tty layer, but tty_print_string 
     654                     * normalizes the string, whereas tty_printf doesn't. Don't normalize, 
     655                     * since we handle combining characters ourselves correctly, it's 
     656                     * better if they are copy-pasted correctly. Ticket 3255. */ 
     657                    tty_printf ("%s", str); 
     658                } 
     659            } 
     660            else if ((off_t) col < dpy_text_column && (off_t) col + charwidth > dpy_text_column) 
     661            { 
     662                /* The combining character sequence would cross the left edge of the viewport. 
     663                 * This cannot happen with wrap mode. Print replacement character(s), 
     664                 * or spaces with the correct attributes for partial Tabs. */ 
     665                tty_setcolor (color); 
     666                for (i = dpy_text_column; 
     667                     i < (off_t) col + charwidth && i < dpy_text_column + width; i++) 
     668                { 
     669                    widget_move (view, top + row, left + (i - dpy_text_column)); 
     670                    tty_print_anychar ((cs[0] == '\t') ? ' ' : PARTIAL_CJK_AT_LEFT_MARGIN); 
     671                } 
     672            } 
     673            else if ((off_t) col < dpy_text_column + width && 
     674                     (off_t) col + charwidth > dpy_text_column + width) 
     675            { 
     676                /* The combining character sequence would cross the right edge of the viewport 
     677                 * and we're not wrapping. Print replacement character(s), 
     678                 * or spaces with the correct attributes for partial Tabs. */ 
     679                tty_setcolor (color); 
     680                for (i = col; i < dpy_text_column + width; i++) 
     681                { 
     682                    widget_move (view, top + row, left + (i - dpy_text_column)); 
     683                    tty_print_anychar ((cs[0] == '\t') ? ' ' : PARTIAL_CJK_AT_RIGHT_MARGIN); 
     684                } 
     685            } 
     686        } 
     687 
     688        col += charwidth; 
     689        state->unwrapped_column += charwidth; 
     690 
     691        if (!view->text_wrap_mode && col >= dpy_text_column + width) 
     692        { 
     693            /* Optimization: Fast forward to the end of the line, rather than carefully 
     694             * parsing and then not actually displaying it. */ 
     695            off_t eol = mcview_eol (view, state->offset, mcview_get_filesize (view)); 
     696            mcview_state_machine_init (state, eol); 
     697            return 1; 
     698        } 
     699    } 
     700} 
     701 
     702/* 
     703 * Parse, format and possibly display one paragraph (perhaps not from the beginning). 
     704 * 
     705 * Formatting starts at the given "state" (which encodes the file offset and parser and formatter's 
     706 * internal state). In unwrap mode, this should point to the beginning of the paragraph with the 
     707 * default state, the additional horizontal scrolling is added here. In wrap mode, this may point 
     708 * to the beginning of the line within a paragraph (to display the partial paragraph at the top), 
     709 * with the proper state at that point. 
     710 * 
     711 * Displaying the next paragraph should start at "state"'s new value, or if we displayed the bottom 
     712 * line then state->offset tells the file offset to be shown in the top bar. 
     713 * 
     714 * If "row" is negative, don't display the first abs(row) lines and display the rest from the top. 
     715 * This was a nice idea but it's now unused :) 
     716 * 
     717 * If "row" is too large, don't display the paragraph at all but still return the number of lines. 
     718 * This is used when moving upwards. 
     719 * 
     720 * @param view ... 
     721 * @param state the parser-formatter state machine's state, updated 
     722 * @param row print starting at this row 
     723 * @return the number of rows the paragraphs is wrapped to, that is, 0 if we were already at EOF, 
     724 *   otherwise 1 in unwrap mode, >= 1 in wrap mode. We stop when reaching the bottom of the 
     725 *   viewport, it's not counted how many more lines the paragraph would occupy 
     726 */ 
     727static int 
     728mcview_display_paragraph (mcview_t * view, mcview_state_machine_t * state, int row) 
     729{ 
     730    const screen_dimen height = view->data_area.height; 
     731    int lines = 0; 
     732    gboolean paragraph_ended; 
     733 
     734    while (1) 
     735    { 
     736        lines += mcview_display_line (view, state, row, &paragraph_ended); 
     737        if (paragraph_ended) 
     738            return lines; 
     739 
     740        if (row < (int) height) 
     741        { 
     742            row++; 
     743            /* stop if bottom of screen reached */ 
     744            if (row >= (int) height) 
     745                return lines; 
     746        } 
     747    } 
     748} 
     749 
     750/* 
     751 * Recompute dpy_state_top from dpy_start and dpy_paragraph_skip_lines. Clamp 
     752 * dpy_paragraph_skip_lines if necessary. 
     753 * 
     754 * This method should be called in wrap mode after changing one of the parsing or formatting 
     755 * properties (e.g. window width, encoding, nroff), or when switching to wrap mode from unwrap or 
     756 * hex. 
     757 * 
     758 * If we stayed within the same paragraph then try to keep the vertical offset within that 
     759 * paragraph as well. It might happen though that the paragraph became shorter than our desired 
     760 * vertical position, in that case move to its last row. 
     761 */ 
     762static void 
     763mcview_wrap_fixup (mcview_t * view) 
     764{ 
     765    mcview_state_machine_t state_prev; 
     766    gboolean paragraph_ended; 
     767    int lines = view->dpy_paragraph_skip_lines; 
     768 
     769    if (!view->dpy_wrap_dirty) 
     770        return; 
     771    view->dpy_wrap_dirty = FALSE; 
     772 
     773    view->dpy_paragraph_skip_lines = 0; 
     774    mcview_state_machine_init (&view->dpy_state_top, view->dpy_start); 
     775 
     776    while (lines--) 
     777    { 
     778        state_prev = view->dpy_state_top; 
     779        if (mcview_display_line (view, &view->dpy_state_top, -1, &paragraph_ended) == 0) 
     780            break; 
     781        if (paragraph_ended) 
     782        { 
     783            view->dpy_state_top = state_prev; 
     784            break; 
     785        } 
     786        view->dpy_paragraph_skip_lines++; 
     787    } 
     788} 
     789 
     790/* --------------------------------------------------------------------------------------------- */ 
     791/*** public functions ****************************************************************************/ 
     792/* --------------------------------------------------------------------------------------------- */ 
     793 
     794/* 
     795 * In both wrap and unwrap modes, dpy_start points to the beginning of the paragraph. 
     796 * 
     797 * In unwrap mode, start displaying from this position, probably applying an additional horizontal 
     798 * scroll. 
     799 * 
     800 * In wrap mode, an additional dpy_paragraph_skip_lines lines are skipped from the top of this 
     801 * paragraph. dpy_state_top contains the position and parser-formatter state corresponding to the 
     802 * top left corner so we can just start rendering from here. Unless dpy_wrap_dirty is set in which 
     803 * case dpy_state_top is invalid and we need to recompute first. 
     804 */ 
     805void 
     806mcview_display_text (mcview_t * view) 
     807{ 
     808    const screen_dimen left = view->data_area.left; 
     809    const screen_dimen top = view->data_area.top; 
     810    const screen_dimen height = view->data_area.height; 
     811    int row; 
     812    int n; 
     813    mcview_state_machine_t state; 
     814    gboolean again; 
     815 
     816    do 
     817    { 
     818        again = FALSE; 
     819 
     820        mcview_display_clean (view); 
     821        mcview_display_ruler (view); 
     822 
     823        if (view->text_wrap_mode) 
     824        { 
     825            mcview_wrap_fixup (view); 
     826            state = view->dpy_state_top; 
     827        } 
     828        else 
     829        { 
     830            mcview_state_machine_init (&state, view->dpy_start); 
     831        } 
     832        row = 0; 
     833        while (row < (int) height) 
     834        { 
     835            n = mcview_display_paragraph (view, &state, row); 
     836            if (n == 0) 
     837            { 
     838                /* In the rare case that displaying didn't start at the beginning 
     839                 * of the file, yet there are some empty lines at the bottom, 
     840                 * scroll the file and display again. This happens when e.g. the 
     841                 * window is made bigger, or the file becomes shorter due to 
     842                 * charset change or enabling nroff. */ 
     843                if ((view->text_wrap_mode ? view->dpy_state_top.offset : view->dpy_start) > 0) 
     844                { 
     845                    mcview_ascii_move_up (view, height - row); 
     846                    again = TRUE; 
     847                } 
     848                break; 
     849            } 
     850            row += n; 
     851        } 
     852    } 
     853    while (again); 
     854 
     855    view->dpy_end = state.offset; 
     856    view->dpy_state_bottom = state; 
     857 
     858    if (mcview_show_eof != NULL && mcview_show_eof[0] != '\0') 
     859    { 
     860        while (row < (int) height) 
     861        { 
     862            widget_move (view, top + row, left); 
     863            /* TODO: should make it no wider than the viewport */ 
     864            tty_print_string (mcview_show_eof); 
     865            row++; 
     866        } 
     867    } 
     868} 
     869 
     870/* 
     871 * Move down. 
     872 * 
     873 * It's very simple. Just invisibly format the next "lines" lines, carefully carrying the formatter 
     874 * state in wrap mode. But before each step we need to check if we've already hit the end of the 
     875 * file, in that case we can no longer move. This is done by walking from dpy_state_bottom. 
     876 * 
     877 * Note that this relies on mcview_display_text() setting dpy_state_bottom to its correct value 
     878 * upon rendering the screen contents. So don't call this function from other functions (e.g. at 
     879 * the bottom of mcview_ascii_move_up()) which invalidate this value. 
     880 */ 
     881void 
     882mcview_ascii_move_down (mcview_t * view, off_t lines) 
     883{ 
     884    gboolean paragraph_ended; 
     885 
     886    while (lines--) 
     887    { 
     888        /* See if there's still data below the bottom line, by imaginarily displaying one 
     889         * more line. This takes care of reading more data into growbuf, if required. 
     890         * If the end position didn't advance, we're at EOF and hence bail out. */ 
     891        if (mcview_display_line (view, &view->dpy_state_bottom, -1, &paragraph_ended) == 0) 
     892            break; 
     893 
     894        /* Okay, there's enough data. Move by 1 row at the top, too. No need to check for 
     895         * EOF, that can't happen. */ 
     896        if (!view->text_wrap_mode) 
     897        { 
     898            view->dpy_start = mcview_eol (view, view->dpy_start, mcview_get_filesize (view)); 
     899            view->dpy_paragraph_skip_lines = 0; 
     900            view->dpy_wrap_dirty = TRUE; 
     901        } 
     902        else 
     903        { 
     904            mcview_display_line (view, &view->dpy_state_top, -1, &paragraph_ended); 
     905            if (paragraph_ended) 
     906            { 
     907                view->dpy_start = view->dpy_state_top.offset; 
     908                view->dpy_paragraph_skip_lines = 0; 
     909            } 
     910            else 
     911            { 
     912                view->dpy_paragraph_skip_lines++; 
     913            } 
     914        } 
     915    } 
     916} 
     917 
     918/* 
     919 * Move up. 
     920 * 
     921 * Unwrap mode: Piece of cake. Wrap mode: If we'd walk back more than the current line offset 
     922 * within the paragraph, we need to jump back to the previous paragraph and compute its height to 
     923 * see if we start from that paragraph, and repeat this if necessary. Once we're within the desired 
     924 * paragraph, we still need to format it from its beginning to know the state. 
     925 * 
     926 * See the top of this file for comments about MAX_BACKWARDS_WALK_IN_PARAGRAPH. 
     927 * 
     928 * force_max is a nice protection against the rare extreme case that the file underneath us 
     929 * changes, we don't want to endlessly consume a file of maybe full of zeros upon moving upwards. 
     930 */ 
     931void 
     932mcview_ascii_move_up (mcview_t * view, off_t lines) 
     933{ 
     934    int i; 
     935 
     936    if (!view->text_wrap_mode) 
     937    { 
     938        while (lines--) 
     939            view->dpy_start = mcview_bol (view, view->dpy_start - 1, 0); 
     940        view->dpy_paragraph_skip_lines = 0; 
     941        view->dpy_wrap_dirty = TRUE; 
     942    } 
     943    else 
     944    { 
     945        while (lines > view->dpy_paragraph_skip_lines) 
     946        { 
     947            /* We need to go back to the previous paragraph. */ 
     948            if (view->dpy_start == 0) 
     949            { 
     950                /* Oops, we're already in the first paragraph. */ 
     951                view->dpy_paragraph_skip_lines = 0; 
     952                mcview_state_machine_init (&view->dpy_state_top, 0); 
     953                return; 
     954            } 
     955            lines -= view->dpy_paragraph_skip_lines; 
     956            view->force_max = view->dpy_start; 
     957            view->dpy_start = 
     958                mcview_bol (view, view->dpy_start - 1, 
     959                            view->dpy_start - MAX_BACKWARDS_WALK_IN_PARAGRAPH); 
     960            mcview_state_machine_init (&view->dpy_state_top, view->dpy_start); 
     961            /* This is a tricky way of denoting that we're at the end of the paragraph. 
     962             * Normally we'd jump to the next paragraph and reset paragraph_skip_lines. But for 
     963             * walking backwards this is exactly what we need. */ 
     964            view->dpy_paragraph_skip_lines = 
     965                mcview_display_paragraph (view, &view->dpy_state_top, view->data_area.height); 
     966            view->force_max = -1; 
     967        } 
     968 
     969        /* Okay, we have have dpy_start pointing to the desired paragraph, and we still need to 
     970         * walk back "lines" lines from the current "dpy_paragraph_skip_lines" offset. We can't do 
     971         * that, so walk from the beginning of the paragraph. */ 
     972        mcview_state_machine_init (&view->dpy_state_top, view->dpy_start); 
     973        view->dpy_paragraph_skip_lines -= lines; 
     974        for (i = 0; i < view->dpy_paragraph_skip_lines; i++) 
     975            mcview_display_line (view, &view->dpy_state_top, -1, NULL); 
     976    } 
     977} 
     978 
     979/* --------------------------------------------------------------------------------------------- */ 
     980 
     981void 
     982mcview_state_machine_init (mcview_state_machine_t * state, off_t offset) 
     983{ 
     984    memset (state, 0, sizeof (*state)); 
     985    state->offset = offset; 
     986    state->print_lonely_combining = TRUE; 
     987} 
     988 
     989/* --------------------------------------------------------------------------------------------- */ 
  • src/viewer/datasource.c

    diff --git a/src/viewer/datasource.c b/src/viewer/datasource.c
    index 3389ee4..d6da436 100644
    a b mcview_get_ptr_string (mcview_t * view, off_t byte_index) 
    164164/* --------------------------------------------------------------------------------------------- */ 
    165165 
    166166int 
    167 mcview_get_utf (mcview_t * view, off_t byte_index, int *char_width, gboolean * result) 
     167mcview_get_utf (mcview_t * view, off_t byte_index, int *bytes_consumed, gboolean * result) 
    168168{ 
    169169    gchar *str = NULL; 
    170170    int res = -1; 
    mcview_get_utf (mcview_t * view, off_t byte_index, int *char_width, gboolean * r 
    172172    gchar *next_ch = NULL; 
    173173    gchar utf8buf[UTF8_CHAR_LEN + 1]; 
    174174 
    175     *char_width = 0; 
     175    *bytes_consumed = 0; 
    176176    *result = FALSE; 
    177177 
    178178    switch (view->datasource) 
    mcview_get_utf (mcview_t * view, off_t byte_index, int *char_width, gboolean * r 
    218218    if (res < 0) 
    219219    { 
    220220        ch = *str; 
    221         *char_width = 1; 
     221        *bytes_consumed = 1; 
    222222    } 
    223223    else 
    224224    { 
    mcview_get_utf (mcview_t * view, off_t byte_index, int *char_width, gboolean * r 
    226226        /* Calculate UTF-8 char width */ 
    227227        next_ch = g_utf8_next_char (str); 
    228228        if (next_ch) 
    229             *char_width = next_ch - str; 
     229            *bytes_consumed = next_ch - str; 
    230230        else 
    231231            return 0; 
    232232    } 
  • src/viewer/display.c

    diff --git a/src/viewer/display.c b/src/viewer/display.c
    index 00c6ec0..b1bd390 100644
    a b mcview_display (mcview_t * view) 
    251251    { 
    252252        mcview_display_hex (view); 
    253253    } 
    254     else if (view->text_nroff_mode) 
    255     { 
    256         mcview_display_nroff (view); 
    257     } 
    258254    else 
    259255    { 
    260256        mcview_display_text (view); 
  • src/viewer/internal.h

    diff --git a/src/viewer/internal.h b/src/viewer/internal.h
    index 9562c52..fc665db 100644
    a b typedef struct 
    8787    coord_cache_entry_t **cache; 
    8888} coord_cache_t; 
    8989 
     90/* TODO: find a better name. This is not actually a "state machine", 
     91 * but a "state machine's state", but that sounds silly. 
     92 * Could be parser_state, formatter_state... */ 
     93typedef struct 
     94{ 
     95    off_t offset;               /* The file offset at which this is the state. */ 
     96    off_t unwrapped_column;     /* Columns if the paragraph wasn't wrapped, */ 
     97    /* used for positioning TABs in wrapped lines */ 
     98    gboolean nroff_underscore_is_underlined;    /* whether _\b_ is underlined rather than bold */ 
     99    gboolean print_lonely_combining;    /* whether lonely combining marks are printed on a dotted circle */ 
     100} mcview_state_machine_t; 
     101 
    90102struct mcview_nroff_struct; 
    91103 
    92104struct mcview_struct 
    struct mcview_struct 
    141153    /* Display information */ 
    142154    gboolean active;            /* Active or not in QuickView mode */ 
    143155    screen_dimen dpy_frame_size;        /* Size of the frame surrounding the real viewer */ 
    144     off_t dpy_start;            /* Offset of the displayed data */ 
     156    off_t dpy_start;            /* Offset of the displayed data (start of the paragraph in non-hex mode) */ 
    145157    off_t dpy_end;              /* Offset after the displayed data */ 
     158    off_t dpy_paragraph_skip_lines;     /* Extra lines to skip in wrap mode */ 
     159    mcview_state_machine_t dpy_state_top;       /* Parser-formatter state at the topmost visible line in wrap mode */ 
     160    mcview_state_machine_t dpy_state_bottom;    /* Parser-formatter state after the bottomvisible line in wrap mode */ 
     161    gboolean dpy_wrap_dirty;    /* dpy_state_top needs to be recomputed */ 
    146162    off_t dpy_text_column;      /* Number of skipped columns in non-wrap 
    147163                                 * text mode */ 
    148164    off_t hex_cursor;           /* Hexview cursor position in file */ 
    struct mcview_struct 
    153169    struct area ruler_area;     /* Where the ruler is displayed */ 
    154170    struct area data_area;      /* Where the data is displayed */ 
    155171 
     172    ssize_t force_max;          /* Force a max offset, or -1 */ 
     173 
    156174    int dirty;                  /* Number of skipped updates */ 
    157175    gboolean dpy_bbar_dirty;    /* Does the button bar need to be updated? */ 
    158176 
    cb_ret_t mcview_callback (Widget * w, Widget * sender, widget_msg_t msg, int par 
    220238cb_ret_t mcview_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, 
    221239                                 void *data); 
    222240 
     241/* ascii.c: */ 
     242void mcview_display_text (mcview_t *); 
     243void mcview_state_machine_init (mcview_state_machine_t *, off_t); 
     244void mcview_ascii_move_down (mcview_t *, off_t); 
     245void mcview_ascii_move_up (mcview_t *, off_t); 
     246 
    223247/* coord_cache.c: */ 
    224248coord_cache_t *coord_cache_new (void); 
    225249void coord_cache_free (coord_cache_t * cache); 
    void mcview_place_cursor (mcview_t *); 
    308332void mcview_moveto_match (mcview_t *); 
    309333 
    310334/* nroff.c: */ 
    311 void mcview_display_nroff (mcview_t * view); 
    312335int mcview__get_nroff_real_len (mcview_t * view, off_t, off_t p); 
    313  
    314336mcview_nroff_t *mcview_nroff_seq_new_num (mcview_t * view, off_t p); 
    315337mcview_nroff_t *mcview_nroff_seq_new (mcview_t * view); 
    316338void mcview_nroff_seq_free (mcview_nroff_t **); 
    nroff_type_t mcview_nroff_seq_info (mcview_nroff_t *); 
    318340int mcview_nroff_seq_next (mcview_nroff_t *); 
    319341int mcview_nroff_seq_prev (mcview_nroff_t *); 
    320342 
    321  
    322 /* plain.c: */ 
    323 void mcview_display_text (mcview_t *); 
    324  
    325343/* search.c: */ 
    326344mc_search_cbret_t mcview_search_cmd_callback (const void *user_data, gsize char_offset, 
    327345                                              int *current_char); 
  • src/viewer/lib.c

    diff --git a/src/viewer/lib.c b/src/viewer/lib.c
    index 6d51206..a5ab76d 100644
    a b mcview_toggle_magic_mode (mcview_t * view) 
    106106void 
    107107mcview_toggle_wrap_mode (mcview_t * view) 
    108108{ 
    109     if (view->text_wrap_mode) 
    110         view->dpy_start = mcview_bol (view, view->dpy_start, 0); 
    111109    view->text_wrap_mode = !view->text_wrap_mode; 
     110    view->dpy_wrap_dirty = TRUE; 
    112111    view->dpy_bbar_dirty = TRUE; 
    113112    view->dirty++; 
    114113} 
    mcview_toggle_nroff_mode (mcview_t * view) 
    120119{ 
    121120    view->text_nroff_mode = !view->text_nroff_mode; 
    122121    mcview_altered_nroff_flag = 1; 
     122    view->dpy_wrap_dirty = TRUE; 
    123123    view->dpy_bbar_dirty = TRUE; 
    124124    view->dirty++; 
    125125} 
    mcview_toggle_hex_mode (mcview_t * view) 
    144144        widget_want_cursor (WIDGET (view), FALSE); 
    145145    } 
    146146    mcview_altered_hex_mode = 1; 
     147    view->dpy_paragraph_skip_lines = 0; 
     148    view->dpy_wrap_dirty = TRUE; 
    147149    view->dpy_bbar_dirty = TRUE; 
    148150    view->dirty++; 
    149151} 
    mcview_init (mcview_t * view) 
    170172    view->coord_cache = NULL; 
    171173 
    172174    view->dpy_start = 0; 
     175    view->dpy_paragraph_skip_lines = 0; 
     176    mcview_state_machine_init (&view->dpy_state_top, 0); 
     177    view->dpy_wrap_dirty = FALSE; 
     178    view->force_max = -1; 
    173179    view->dpy_text_column = 0; 
    174180    view->dpy_end = 0; 
    175181    view->hex_cursor = 0; 
    mcview_set_codeset (mcview_t * view) 
    282288            view->converter = conv; 
    283289        } 
    284290        view->utf8 = (gboolean) str_isutf8 (cp_id); 
     291        view->dpy_wrap_dirty = TRUE; 
    285292    } 
    286293#else 
    287294    (void) view; 
    mcview_bol (mcview_t * view, off_t current, off_t limit) 
    339346        if (c == '\r') 
    340347            current--; 
    341348    } 
    342     while (current > 0 && current >= limit) 
     349    while (current > 0 && current > limit) 
    343350    { 
    344351        if (!mcview_get_byte (view, current - 1, &c)) 
    345352            break; 
  • src/viewer/mcviewer.c

    diff --git a/src/viewer/mcviewer.c b/src/viewer/mcviewer.c
    index eb8ec73..dafbc21 100644
    a b mcview_load (mcview_t * view, const char *command, const char *file, int start_l 
    402402  finish: 
    403403    view->command = g_strdup (command); 
    404404    view->dpy_start = 0; 
     405    view->dpy_paragraph_skip_lines = 0; 
     406    mcview_state_machine_init (&view->dpy_state_top, 0); 
     407    view->dpy_wrap_dirty = FALSE; 
     408    view->force_max = -1; 
    405409    view->search_start = 0; 
    406410    view->search_end = 0; 
    407411    view->dpy_text_column = 0; 
    mcview_load (mcview_t * view, const char *command, const char *file, int start_l 
    421425        else 
    422426            new_offset = min (new_offset, max_offset); 
    423427        if (!view->hex_mode) 
     428        { 
    424429            view->dpy_start = mcview_bol (view, new_offset, 0); 
     430            view->dpy_wrap_dirty = TRUE; 
     431        } 
    425432        else 
    426433        { 
    427434            view->dpy_start = new_offset - new_offset % view->bytes_per_line; 
  • src/viewer/move.c

    diff --git a/src/viewer/move.c b/src/viewer/move.c
    index 7cd852b..b7938ac 100644
    a b mcview_scroll_to_cursor (mcview_t * view) 
    8383        if (cursor < topleft) 
    8484            topleft = mcview_offset_rounddown (cursor, bytes); 
    8585        view->dpy_start = topleft; 
     86        view->dpy_paragraph_skip_lines = 0; 
     87        view->dpy_wrap_dirty = TRUE; 
    8688    } 
    8789} 
    8890 
    mcview_movement_fixups (mcview_t * view, gboolean reset_search) 
    107109void 
    108110mcview_move_up (mcview_t * view, off_t lines) 
    109111{ 
    110     off_t new_offset; 
    111  
    112112    if (view->hex_mode) 
    113113    { 
    114114        off_t bytes = lines * view->bytes_per_line; 
    mcview_move_up (mcview_t * view, off_t lines) 
    116116        { 
    117117            view->hex_cursor -= bytes; 
    118118            if (view->hex_cursor < view->dpy_start) 
     119            { 
    119120                view->dpy_start = mcview_offset_doz (view->dpy_start, bytes); 
     121                view->dpy_paragraph_skip_lines = 0; 
     122                view->dpy_wrap_dirty = TRUE; 
     123            } 
    120124        } 
    121125        else 
    122126        { 
    mcview_move_up (mcview_t * view, off_t lines) 
    125129    } 
    126130    else 
    127131    { 
    128         off_t i; 
    129  
    130         for (i = 0; i < lines; i++) 
    131         { 
    132             if (view->dpy_start == 0) 
    133                 break; 
    134             if (view->text_wrap_mode) 
    135             { 
    136                 new_offset = mcview_bol (view, view->dpy_start, view->dpy_start - (off_t) 1); 
    137                 /* check if dpy_start == BOL or not (then new_offset = dpy_start - 1, 
    138                  * no need to check more) */ 
    139                 if (new_offset == view->dpy_start) 
    140                 { 
    141                     size_t last_row_length; 
    142  
    143                     new_offset = mcview_bol (view, new_offset - 1, 0); 
    144                     last_row_length = (view->dpy_start - new_offset) % view->data_area.width; 
    145                     if (last_row_length != 0) 
    146                     { 
    147                         /* if dpy_start == BOL in wrapped mode, find BOL of previous line 
    148                          * and move down all but the last rows */ 
    149                         new_offset = view->dpy_start - (off_t) last_row_length; 
    150                     } 
    151                 } 
    152                 else 
    153                 { 
    154                     /* if dpy_start != BOL in wrapped mode, just move one row up; 
    155                      * no need to check if > 0 as there is at least exactly one wrap 
    156                      * between dpy_start and BOL */ 
    157                     new_offset = view->dpy_start - (off_t) view->data_area.width; 
    158                 } 
    159                 view->dpy_start = new_offset; 
    160             } 
    161             else 
    162             { 
    163                 /* if unwrapped -> current BOL equals dpy_start, just find BOL of previous line */ 
    164                 new_offset = view->dpy_start - 1; 
    165                 view->dpy_start = mcview_bol (view, new_offset, 0); 
    166             } 
    167         } 
     132        mcview_ascii_move_up (view, lines); 
    168133    } 
    169134    mcview_movement_fixups (view, TRUE); 
    170135} 
    mcview_move_down (mcview_t * view, off_t lines) 
    188153        { 
    189154            view->hex_cursor += view->bytes_per_line; 
    190155            if (lines != 1) 
     156            { 
    191157                view->dpy_start += view->bytes_per_line; 
     158                view->dpy_paragraph_skip_lines = 0; 
     159                view->dpy_wrap_dirty = TRUE; 
     160            } 
    192161        } 
    193162    } 
    194163    else 
    195164    { 
    196         off_t new_offset = 0; 
    197  
    198         if (view->dpy_end - view->dpy_start > last_byte - view->dpy_end) 
    199         { 
    200             while (lines-- > 0) 
    201             { 
    202                 if (view->text_wrap_mode) 
    203                     view->dpy_end = 
    204                         mcview_eol (view, view->dpy_end, 
    205                                     view->dpy_end + (off_t) view->data_area.width); 
    206                 else 
    207                     view->dpy_end = mcview_eol (view, view->dpy_end, last_byte); 
    208  
    209                 if (view->text_wrap_mode) 
    210                     new_offset = 
    211                         mcview_eol (view, view->dpy_start, 
    212                                     view->dpy_start + (off_t) view->data_area.width); 
    213                 else 
    214                     new_offset = mcview_eol (view, view->dpy_start, last_byte); 
    215                 if (new_offset < last_byte) 
    216                     view->dpy_start = new_offset; 
    217                 if (view->dpy_end >= last_byte) 
    218                     break; 
    219             } 
    220         } 
    221         else 
    222         { 
    223             off_t i; 
    224             for (i = 0; i < lines && new_offset < last_byte; i++) 
    225             { 
    226                 if (view->text_wrap_mode) 
    227                     new_offset = 
    228                         mcview_eol (view, view->dpy_start, 
    229                                     view->dpy_start + (off_t) view->data_area.width); 
    230                 else 
    231                     new_offset = mcview_eol (view, view->dpy_start, last_byte); 
    232                 if (new_offset < last_byte) 
    233                     view->dpy_start = new_offset; 
    234             } 
    235         } 
     165        mcview_ascii_move_down (view, lines); 
    236166    } 
    237167    mcview_movement_fixups (view, TRUE); 
    238168} 
    mcview_move_left (mcview_t * view, off_t columns) 
    257187            if (old_cursor > 0 || view->hexedit_lownibble) 
    258188                view->hexedit_lownibble = !view->hexedit_lownibble; 
    259189    } 
    260     else 
     190    else if (!view->text_wrap_mode) 
    261191    { 
    262192        if (view->dpy_text_column >= columns) 
    263193            view->dpy_text_column -= columns; 
    mcview_move_right (mcview_t * view, off_t columns) 
    289219            if (old_cursor < last_byte || !view->hexedit_lownibble) 
    290220                view->hexedit_lownibble = !view->hexedit_lownibble; 
    291221    } 
    292     else 
     222    else if (!view->text_wrap_mode) 
    293223    { 
    294224        view->dpy_text_column += columns; 
    295225    } 
    void 
    302232mcview_moveto_top (mcview_t * view) 
    303233{ 
    304234    view->dpy_start = 0; 
     235    view->dpy_paragraph_skip_lines = 0; 
     236    mcview_state_machine_init (&view->dpy_state_top, 0); 
    305237    view->hex_cursor = 0; 
    306238    view->dpy_text_column = 0; 
    307239    mcview_movement_fixups (view, TRUE); 
    mcview_moveto_bottom (mcview_t * view) 
    331263        const off_t datalines = view->data_area.height; 
    332264 
    333265        view->dpy_start = filesize; 
     266        view->dpy_paragraph_skip_lines = 0; 
     267        view->dpy_wrap_dirty = TRUE; 
    334268        mcview_move_up (view, datalines); 
    335269    } 
    336270} 
    mcview_moveto_bol (mcview_t * view) 
    347281    else if (!view->text_wrap_mode) 
    348282    { 
    349283        view->dpy_start = mcview_bol (view, view->dpy_start, 0); 
     284        view->dpy_paragraph_skip_lines = 0; 
     285        view->dpy_wrap_dirty = TRUE; 
    350286    } 
    351287    view->dpy_text_column = 0; 
    352288    mcview_movement_fixups (view, TRUE); 
    mcview_moveto_offset (mcview_t * view, off_t offset) 
    424360    { 
    425361        view->hex_cursor = offset; 
    426362        view->dpy_start = offset - offset % view->bytes_per_line; 
     363        view->dpy_paragraph_skip_lines = 0; 
     364        view->dpy_wrap_dirty = TRUE; 
    427365    } 
    428366    else 
    429367    { 
    430368        view->dpy_start = offset; 
     369        view->dpy_paragraph_skip_lines = 0; 
     370        view->dpy_wrap_dirty = TRUE; 
    431371    } 
    432372    mcview_movement_fixups (view, TRUE); 
    433373} 
    mcview_moveto_match (mcview_t * view) 
    498438        view->hexedit_lownibble = FALSE; 
    499439        view->dpy_start = view->search_start - view->search_start % view->bytes_per_line; 
    500440        view->dpy_end = view->search_end - view->search_end % view->bytes_per_line; 
     441        view->dpy_paragraph_skip_lines = 0; 
     442        view->dpy_wrap_dirty = TRUE; 
    501443    } 
    502444    else 
     445    { 
    503446        view->dpy_start = mcview_bol (view, view->search_start, 0); 
     447        view->dpy_paragraph_skip_lines = 0; 
     448        view->dpy_wrap_dirty = TRUE; 
     449    } 
    504450 
    505451    mcview_scroll_to_cursor (view); 
    506452    view->dirty++; 
  • src/viewer/nroff.c

    diff --git a/src/viewer/nroff.c b/src/viewer/nroff.c
    index 6d6c97b..e1f5010 100644
    a b  
    11/* 
    22   Internal file viewer for the Midnight Commander 
    3    Function for nroff-like view 
     3   Functions for searching in nroff-like view 
    44 
    55   Copyright (C) 1994-2014 
    66   Free Software Foundation, Inc. 
    mcview_nroff_get_char (mcview_nroff_t * nroff, int *ret_val, off_t nroff_index) 
    9191/*** public functions ****************************************************************************/ 
    9292/* --------------------------------------------------------------------------------------------- */ 
    9393 
    94 void 
    95 mcview_display_nroff (mcview_t * view) 
    96 { 
    97     const screen_dimen left = view->data_area.left; 
    98     const screen_dimen top = view->data_area.top; 
    99     const screen_dimen width = view->data_area.width; 
    100     const screen_dimen height = view->data_area.height; 
    101     screen_dimen row, col; 
    102     off_t from; 
    103     int cw = 1; 
    104     int c; 
    105     int c_prev = 0; 
    106     int c_next = 0; 
    107  
    108     mcview_display_clean (view); 
    109     mcview_display_ruler (view); 
    110  
    111     /* Find the first displayable changed byte */ 
    112     from = view->dpy_start; 
    113  
    114     tty_setcolor (VIEW_NORMAL_COLOR); 
    115     for (row = 0, col = 0; row < height;) 
    116     { 
    117 #ifdef HAVE_CHARSET 
    118         if (view->utf8) 
    119         { 
    120             gboolean read_res = TRUE; 
    121             c = mcview_get_utf (view, from, &cw, &read_res); 
    122             if (!read_res) 
    123                 break; 
    124         } 
    125         else 
    126 #endif 
    127         { 
    128             if (!mcview_get_byte (view, from, &c)) 
    129                 break; 
    130         } 
    131         from++; 
    132         if (cw > 1) 
    133             from += cw - 1; 
    134  
    135         if (c == '\b') 
    136         { 
    137             if (from > 1) 
    138             { 
    139 #ifdef HAVE_CHARSET 
    140                 if (view->utf8) 
    141                 { 
    142                     gboolean read_res; 
    143                     c_next = mcview_get_utf (view, from, &cw, &read_res); 
    144                 } 
    145                 else 
    146 #endif 
    147                     mcview_get_byte (view, from, &c_next); 
    148             } 
    149             if (g_unichar_isprint (c_prev) && g_unichar_isprint (c_next) 
    150                 && (c_prev == c_next || c_prev == '_' || (c_prev == '+' && c_next == 'o'))) 
    151             { 
    152                 if (col == 0) 
    153                 { 
    154                     if (row == 0) 
    155                     { 
    156                         /* We're inside an nroff character sequence at the 
    157                          * beginning of the screen -- just skip the 
    158                          * backspace and continue with the next character. */ 
    159                         continue; 
    160                     } 
    161                     row--; 
    162                     col = width; 
    163                 } 
    164                 col--; 
    165                 if (c_prev == '_' 
    166                     && (c_next != '_' || mcview_count_backspaces (view, from + 1) == 1)) 
    167                     tty_setcolor (VIEW_UNDERLINED_COLOR); 
    168                 else 
    169                     tty_setcolor (VIEW_BOLD_COLOR); 
    170                 continue; 
    171             } 
    172         } 
    173  
    174         if ((c == '\n') || (col >= width && view->text_wrap_mode)) 
    175         { 
    176             col = 0; 
    177             row++; 
    178             if (c == '\n' || row >= height) 
    179                 continue; 
    180         } 
    181  
    182         if (c == '\r') 
    183         { 
    184             mcview_get_byte_indexed (view, from, 1, &c); 
    185             if (c == '\r' || c == '\n') 
    186                 continue; 
    187             col = 0; 
    188             row++; 
    189             continue; 
    190         } 
    191  
    192         if (c == '\t') 
    193         { 
    194             off_t line, column; 
    195             mcview_offset_to_coord (view, &line, &column, from); 
    196             col += (option_tab_spacing - col % option_tab_spacing); 
    197             if (view->text_wrap_mode && col >= width && width != 0) 
    198             { 
    199                 row += col / width; 
    200                 col %= width; 
    201             } 
    202             continue; 
    203         } 
    204  
    205         if (view->search_start <= from && from < view->search_end) 
    206         { 
    207             tty_setcolor (SELECTED_COLOR); 
    208         } 
    209  
    210         c_prev = c; 
    211  
    212         if ((off_t) col >= view->dpy_text_column 
    213             && (off_t) col - view->dpy_text_column < (off_t) width) 
    214         { 
    215             widget_move (view, top + row, left + ((off_t) col - view->dpy_text_column)); 
    216 #ifdef HAVE_CHARSET 
    217             if (mc_global.utf8_display) 
    218             { 
    219                 if (!view->utf8) 
    220                 { 
    221                     c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter); 
    222                 } 
    223                 if (!g_unichar_isprint (c)) 
    224                     c = '.'; 
    225             } 
    226             else if (view->utf8) 
    227                 c = convert_from_utf_to_current_c (c, view->converter); 
    228             else 
    229                 c = convert_to_display_c (c); 
    230 #endif 
    231             tty_print_anychar (c); 
    232         } 
    233         col++; 
    234 #ifdef HAVE_CHARSET 
    235         if (view->utf8) 
    236         { 
    237             if (g_unichar_iswide (c)) 
    238                 col++; 
    239             else if (g_unichar_iszerowidth (c)) 
    240                 col--; 
    241         } 
    242 #endif 
    243         tty_setcolor (VIEW_NORMAL_COLOR); 
    244     } 
    245     view->dpy_end = from; 
    246 } 
    247  
    248 /* --------------------------------------------------------------------------------------------- */ 
    249  
    25094int 
    25195mcview__get_nroff_real_len (mcview_t * view, off_t start, off_t length) 
    25296{ 
  • deleted file src/viewer/plain.c

    diff --git a/src/viewer/plain.c b/src/viewer/plain.c
    deleted file mode 100644
    index 11e65d4..0000000
    + -  
    1 /* 
    2    Internal file viewer for the Midnight Commander 
    3    Function for plain view 
    4  
    5    Copyright (C) 1994-2014 
    6    Free Software Foundation, Inc. 
    7  
    8    Written by: 
    9    Miguel de Icaza, 1994, 1995, 1998 
    10    Janne Kukonlehto, 1994, 1995 
    11    Jakub Jelinek, 1995 
    12    Joseph M. Hinkle, 1996 
    13    Norbert Warmuth, 1997 
    14    Pavel Machek, 1998 
    15    Roland Illig <roland.illig@gmx.de>, 2004, 2005 
    16    Slava Zanko <slavazanko@google.com>, 2009 
    17    Andrew Borodin <aborodin@vmail.ru>, 2009-2014 
    18    Ilia Maslakov <il.smind@gmail.com>, 2009 
    19  
    20    This file is part of the Midnight Commander. 
    21  
    22    The Midnight Commander is free software: you can redistribute it 
    23    and/or modify it under the terms of the GNU General Public License as 
    24    published by the Free Software Foundation, either version 3 of the License, 
    25    or (at your option) any later version. 
    26  
    27    The Midnight Commander is distributed in the hope that it will be useful, 
    28    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    29    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    30    GNU General Public License for more details. 
    31  
    32    You should have received a copy of the GNU General Public License 
    33    along with this program.  If not, see <http://www.gnu.org/licenses/>. 
    34  */ 
    35  
    36 #include <config.h> 
    37  
    38 #include "lib/global.h" 
    39 #include "lib/tty/tty.h" 
    40 #include "lib/skin.h" 
    41 #include "lib/util.h"           /* is_printable() */ 
    42 #ifdef HAVE_CHARSET 
    43 #include "lib/charsets.h" 
    44 #endif 
    45  
    46 #include "src/setup.h"          /* option_tab_spacing */ 
    47  
    48 #include "internal.h" 
    49  
    50 /*** global variables ****************************************************************************/ 
    51  
    52 /*** file scope macro definitions ****************************************************************/ 
    53  
    54 /*** file scope type declarations ****************************************************************/ 
    55  
    56 /*** file scope variables ************************************************************************/ 
    57  
    58 /*** file scope functions ************************************************************************/ 
    59 /* --------------------------------------------------------------------------------------------- */ 
    60  
    61 /* --------------------------------------------------------------------------------------------- */ 
    62 /*** public functions ****************************************************************************/ 
    63 /* --------------------------------------------------------------------------------------------- */ 
    64  
    65 void 
    66 mcview_display_text (mcview_t * view) 
    67 { 
    68     const screen_dimen left = view->data_area.left; 
    69     const screen_dimen top = view->data_area.top; 
    70     const screen_dimen width = view->data_area.width; 
    71     const screen_dimen height = view->data_area.height; 
    72     screen_dimen row = 0, col = 0; 
    73     off_t from; 
    74     int cw = 1; 
    75     int c, prev_ch = 0; 
    76     gboolean last_row = TRUE; 
    77  
    78     mcview_display_clean (view); 
    79     mcview_display_ruler (view); 
    80  
    81     /* Find the first displayable changed byte */ 
    82     from = view->dpy_start; 
    83  
    84     while (row < height) 
    85     { 
    86 #ifdef HAVE_CHARSET 
    87         if (view->utf8) 
    88         { 
    89             gboolean read_res = TRUE; 
    90  
    91             c = mcview_get_utf (view, from, &cw, &read_res); 
    92             if (!read_res) 
    93                 break; 
    94         } 
    95         else 
    96 #endif 
    97         if (!mcview_get_byte (view, from, &c)) 
    98             break; 
    99  
    100         last_row = FALSE; 
    101         from++; 
    102         if (cw > 1) 
    103             from += cw - 1; 
    104  
    105         if (c != '\n' && prev_ch == '\r') 
    106         { 
    107             if (++row >= height) 
    108                 break; 
    109  
    110             col = 0; 
    111             /* tty_print_anychar ('\n'); */ 
    112         } 
    113  
    114         prev_ch = c; 
    115         if (c == '\r') 
    116             continue; 
    117  
    118         if (c == '\n') 
    119         { 
    120             col = 0; 
    121             row++; 
    122             continue; 
    123         } 
    124  
    125         if (col >= width && view->text_wrap_mode) 
    126         { 
    127             col = 0; 
    128             if (++row >= height) 
    129                 break; 
    130         } 
    131  
    132         if (c == '\t') 
    133         { 
    134             col += (option_tab_spacing - col % option_tab_spacing); 
    135             if (view->text_wrap_mode && col >= width && width != 0) 
    136             { 
    137                 row += col / width; 
    138                 col %= width; 
    139             } 
    140             continue; 
    141         } 
    142  
    143         if (view->search_start <= from && from < view->search_end) 
    144             tty_setcolor (SELECTED_COLOR); 
    145         else 
    146             tty_setcolor (VIEW_NORMAL_COLOR); 
    147  
    148         if (((off_t) col >= view->dpy_text_column) 
    149             && ((off_t) col - view->dpy_text_column < (off_t) width)) 
    150         { 
    151             widget_move (view, top + row, left + ((off_t) col - view->dpy_text_column)); 
    152  
    153 #ifdef HAVE_CHARSET 
    154             if (mc_global.utf8_display) 
    155             { 
    156                 if (!view->utf8) 
    157                     c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter); 
    158                 if (!g_unichar_isprint (c)) 
    159                     c = '.'; 
    160             } 
    161             else if (view->utf8) 
    162                 c = convert_from_utf_to_current_c (c, view->converter); 
    163             else 
    164             { 
    165                 c = convert_to_display_c (c); 
    166                 if (!is_printable (c)) 
    167                     c = '.'; 
    168             } 
    169 #else /* HAVE_CHARSET */ 
    170             if (!is_printable (c)) 
    171                 c = '.'; 
    172 #endif /* HAVE_CHARSET */ 
    173  
    174             tty_print_anychar (c); 
    175         } 
    176  
    177         col++; 
    178  
    179 #ifdef HAVE_CHARSET 
    180         if (view->utf8) 
    181         { 
    182             if (g_unichar_iswide (c)) 
    183                 col++; 
    184             else if (g_unichar_iszerowidth (c)) 
    185                 col--; 
    186         } 
    187 #endif 
    188     } 
    189  
    190     view->dpy_end = from; 
    191     if (mcview_show_eof != NULL && mcview_show_eof[0] != '\0') 
    192     { 
    193         if (last_row && mcview_get_byte (view, from - 1, &c) && c != '\n') 
    194             row--; 
    195  
    196         while (++row < height) 
    197         { 
    198             widget_move (view, top + row, left); 
    199             tty_print_string (mcview_show_eof); 
    200         } 
    201     } 
    202 } 
    203  
    204 /* --------------------------------------------------------------------------------------------- */ 
  • new file tests/src/viewer/viewertest.txt

    diff --git a/tests/src/viewer/viewertest.txt b/tests/src/viewer/viewertest.txt
    new file mode 100644
    index 0000000000000000000000000000000000000000..add62846d0097181ccfd117df40719441a85be28
    GIT binary patch
    literal 4680
    zcmdTIYjYe&k?`DG5D?yPI-iijNtTl6VHH*2mn>Vdlh|^U@Y>qlo4eb1Z}+mZr#q##
    zirte5U?~a2A+||K_#hH0RQZcB4iuEC_VfIR6pI}5FX*1xTiq!#6_@pe(Oyq?PxtIh
    zPd{e$AS}*-$-#9z1|bU?j%S8`pfA-O4uRi>Iu1@B1lxC84Uf+Pz{BGaLTZl`*kplC
    z71(qSn?4u~&NnlDPxHad`0fcO?rlPtnTT}#K{#?|4_gfFvn>Ya+Xq|hns6U%F*HQB
    z`0k#rF*JCV_wF0JCfpB$3@&kIKP;jTp?@qLIt+H*3``qCg@a#(6*E|Myc)EfN?7MG
    z+;+k`1gy#e=GhD}ufhTnFfD(Djg094%XQ{KJ20C~Rw+YR$G8hj2LrGMmWi{r9oGfV
    z55aWX<|@a|sd@TL-qM?cf765H)Kot`IXSsQK7y@BJxomz56MROglg%{sCZjxx->Oa
    z!f8sRU6N`grBTYoODb~whUIv2qqfbw5RKzO=r_qmY=s4@z?toP6>wRIwu51_40ajD
    z#>PhU(YtSven=#TkBIo_F_Fw27xDZFk(^u*@!~0woPJcqk3BAuGiOD-bWS8sEQ|Qb
    zr$qAXs)##nk*u^t9CDEa&xp9`i^OY)*gY?jryUX3Ya*#K5m#)HSf+^O=1%zm0}~hD
    zAjIBlt0r*Vyo;kf_n>V7+yO-T_^b&qF^jJNc!)ZYU}{x}=wfc|p<)eSa)%HXNVh|}
    z9i@w%(v^tP%~7RW5Tr|4?tL3DL$FxQ@#F#GiBUBu%N?6lI<3&O%IKG29o=^?=N@LA
    zrt8>F2o)cXw*s~k*e>I04&F3v#z#~L=x7w#?$}5Azb<d6JxRg4rXroROM3vh{j+9+
    z$;|*Qj9j*12IuvEzZ0v4{cl9B?%hAGD_LCI2gD;C1xver>Nrj=lwlD)s{u}vw;Eur
    z!g1dxXTrqban}s-FnZxbb(+^5JeCRxqTi8q?=r7YLdlYAS$8vAyPS1zWNUxezVcj_
    zJfFo0^547sYL;we$)C6X{#Mpq&yo$~-@4kpb@9r37yo+eI?8NpU;NAM^*6KRPd(v{
    z?SK9QRkGwlmb{oHn^}U2*S0s`qG^-2w_kZJORi_#mu|nk^`GSBEV(LYzJxQ-{D~Cn
    zuHU|KGfOVXp=-p9yAbK{|FTwjo=4e=>6tY%a0VO<gJD;3X!_g<9pB?)aI(cQ-&5|#
    z@^J(m0s~D}hPDqjb6t*S?zh|uP}MMl0~LoW40CLap=2Qams~ghMbeE2fMQ7j*nv4W
    zXY_UGv73J*@1|~ZOLhen<zv0U-e^u2!^L<Z@IHwhksX<y5T&c7y6M1KEV^*0fk_op
    z>V7?uZod1@b+8E71S$j=fhvI-fjR+)z|#cI6L1MM2zUg10!;$X5C{lx0wIAGffWL6
    z0v!UY1fE48JqE8892!msLs~k3PX~%93ow)R>IqkM67x<kbIQY|JcLN`sJujlM`6lZ
    zfKg;xc7-AHzFY)@s??tWO0o!9GHni=ysGfWIw7VgOg`9gj^7GAoW%<ZXT6dxm)>Z(
    zq0_|FHEfR*WyZ&P&qw-_t=x+&j#T;;Qjo;Tc@q7=M5NoQCfd{p6^ZDIHE{ue&vs;@
    z=F}vPWbxoQ=TfI&DP;iUN)1I9!>D1?pPWj>$QKC#tlW~1@95{^&`#~aIG4A<GFYv!
    z2zg~oI&M5@d9-TxIf`9j6vC+$CR2>NBFR|j0d$2RYvn}0!Eh9pElq<aQxr0ar!ZRo
    z&@QvdH_)X0TFP)rtKUe4>7*8F<R(Q-8t-)ASF0)tlxdU=aP!53CdmI}B13-BV?^v~
    zk#5XmZuQH?vLCpW2Mib}Gyv*WCL;oD+8I<N?B%Jw5+i_NScYM=@R#@a9?h87=9$J^
    z&Be#p{IXU?rlBnxnr~=<p}B@uF|-GeDOaJP4KO%0{23Y+X$0LHxFQYLq|qWYT#cpS
    z+6v0hYy%1WB_JouaaygW0$M{;7Ubp@a<@<v#}MF14@EBGxH<#hpb!aL1->U=?o)}t
    z_eOga%VJ>pRJm%V<5508;jQ2&O$9tUPW%u+$G6YRTMSrq)9j;(YS9-Ix9R4+Bj~ZV
    zyrbUXKI-7N2Yy#oodC-!{ug6JH^(fQ_Qvw$XN>8lW|g#(p{Xme7Y4rDmu+Eqe0+3b
    z4p_%#&5+i0z^xvzgECCdmGM6iqX*|k@K%;rW&C2peFm)Q;$=);%<v-2^&cEMeB|h{
    zx#RPrqYI0pryqUn@iS+a&ONdG<WoQV(T{)fQzQC*REV57xv+R@4-x%C_kH*yAN|<J
    zKcNA9@>B5X&wTd&&wc(2U;NUSzw*_uef=BX{MNUlpG9e$CTTZaOE089PoGPFk^VCM
    zReCX9PdC!%(-+bg(_g2*Nnc7Yr7x$yO<zebr&rRe>F?54)7R49r?01P$nff&%ekew
    l(HZ#xN;w{PgT)9hpe@Ywr|_cO;;_)Fd*8t;$xbEE{|kv)4EF#4