summaryrefslogtreecommitdiffstats
path: root/src/viewer
diff options
context:
space:
mode:
Diffstat (limited to 'src/viewer')
-rw-r--r--src/viewer/Makefile.am21
-rw-r--r--src/viewer/Makefile.in793
-rw-r--r--src/viewer/actions_cmd.c790
-rw-r--r--src/viewer/ascii.c1051
-rw-r--r--src/viewer/coord_cache.c395
-rw-r--r--src/viewer/datasource.c434
-rw-r--r--src/viewer/dialogs.c263
-rw-r--r--src/viewer/display.c404
-rw-r--r--src/viewer/growbuf.c297
-rw-r--r--src/viewer/hex.c484
-rw-r--r--src/viewer/internal.h472
-rw-r--r--src/viewer/lib.c437
-rw-r--r--src/viewer/mcviewer.c469
-rw-r--r--src/viewer/mcviewer.h57
-rw-r--r--src/viewer/move.c415
-rw-r--r--src/viewer/nroff.c287
-rw-r--r--src/viewer/search.c491
17 files changed, 7560 insertions, 0 deletions
diff --git a/src/viewer/Makefile.am b/src/viewer/Makefile.am
new file mode 100644
index 0000000..9bf1648
--- /dev/null
+++ b/src/viewer/Makefile.am
@@ -0,0 +1,21 @@
+
+noinst_LTLIBRARIES = libmcviewer.la
+
+libmcviewer_la_SOURCES = \
+ actions_cmd.c \
+ ascii.c \
+ coord_cache.c \
+ datasource.c \
+ dialogs.c \
+ display.c \
+ growbuf.c \
+ hex.c \
+ internal.h \
+ lib.c \
+ mcviewer.c \
+ mcviewer.h \
+ move.c \
+ nroff.c \
+ search.c
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/src/viewer/Makefile.in b/src/viewer/Makefile.in
new file mode 100644
index 0000000..26ff9c6
--- /dev/null
+++ b/src/viewer/Makefile.in
@@ -0,0 +1,793 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/viewer
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmcviewer_la_LIBADD =
+am_libmcviewer_la_OBJECTS = actions_cmd.lo ascii.lo coord_cache.lo \
+ datasource.lo dialogs.lo display.lo growbuf.lo hex.lo lib.lo \
+ mcviewer.lo move.lo nroff.lo search.lo
+libmcviewer_la_OBJECTS = $(am_libmcviewer_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/actions_cmd.Plo \
+ ./$(DEPDIR)/ascii.Plo ./$(DEPDIR)/coord_cache.Plo \
+ ./$(DEPDIR)/datasource.Plo ./$(DEPDIR)/dialogs.Plo \
+ ./$(DEPDIR)/display.Plo ./$(DEPDIR)/growbuf.Plo \
+ ./$(DEPDIR)/hex.Plo ./$(DEPDIR)/lib.Plo \
+ ./$(DEPDIR)/mcviewer.Plo ./$(DEPDIR)/move.Plo \
+ ./$(DEPDIR)/nroff.Plo ./$(DEPDIR)/search.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmcviewer_la_SOURCES)
+DIST_SOURCES = $(libmcviewer_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmcviewer.la
+libmcviewer_la_SOURCES = \
+ actions_cmd.c \
+ ascii.c \
+ coord_cache.c \
+ datasource.c \
+ dialogs.c \
+ display.c \
+ growbuf.c \
+ hex.c \
+ internal.h \
+ lib.c \
+ mcviewer.c \
+ mcviewer.h \
+ move.c \
+ nroff.c \
+ search.c
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/viewer/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/viewer/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmcviewer.la: $(libmcviewer_la_OBJECTS) $(libmcviewer_la_DEPENDENCIES) $(EXTRA_libmcviewer_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcviewer_la_OBJECTS) $(libmcviewer_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/actions_cmd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ascii.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/coord_cache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/datasource.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dialogs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/display.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/growbuf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hex.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mcviewer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/move.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nroff.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/search.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/actions_cmd.Plo
+ -rm -f ./$(DEPDIR)/ascii.Plo
+ -rm -f ./$(DEPDIR)/coord_cache.Plo
+ -rm -f ./$(DEPDIR)/datasource.Plo
+ -rm -f ./$(DEPDIR)/dialogs.Plo
+ -rm -f ./$(DEPDIR)/display.Plo
+ -rm -f ./$(DEPDIR)/growbuf.Plo
+ -rm -f ./$(DEPDIR)/hex.Plo
+ -rm -f ./$(DEPDIR)/lib.Plo
+ -rm -f ./$(DEPDIR)/mcviewer.Plo
+ -rm -f ./$(DEPDIR)/move.Plo
+ -rm -f ./$(DEPDIR)/nroff.Plo
+ -rm -f ./$(DEPDIR)/search.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/actions_cmd.Plo
+ -rm -f ./$(DEPDIR)/ascii.Plo
+ -rm -f ./$(DEPDIR)/coord_cache.Plo
+ -rm -f ./$(DEPDIR)/datasource.Plo
+ -rm -f ./$(DEPDIR)/dialogs.Plo
+ -rm -f ./$(DEPDIR)/display.Plo
+ -rm -f ./$(DEPDIR)/growbuf.Plo
+ -rm -f ./$(DEPDIR)/hex.Plo
+ -rm -f ./$(DEPDIR)/lib.Plo
+ -rm -f ./$(DEPDIR)/mcviewer.Plo
+ -rm -f ./$(DEPDIR)/move.Plo
+ -rm -f ./$(DEPDIR)/nroff.Plo
+ -rm -f ./$(DEPDIR)/search.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/viewer/actions_cmd.c b/src/viewer/actions_cmd.c
new file mode 100644
index 0000000..465f0f0
--- /dev/null
+++ b/src/viewer/actions_cmd.c
@@ -0,0 +1,790 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Callback function for some actions (hotkeys, menu)
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ The functions in this section can be bound to hotkeys. They are all
+ of the same type (taking a pointer to WView as parameter and
+ returning void). TODO: In the not-too-distant future, these commands
+ will become fully configurable, like they already are in the
+ internal editor. By convention, all the function names end in
+ "_cmd".
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/lock.h" /* lock_file() */
+#include "lib/file-entry.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+#include "lib/event.h" /* mc_event_raise() */
+#include "lib/mcconfig.h" /* mc_config_history_get() */
+
+#include "src/filemanager/layout.h"
+#include "src/filemanager/filemanager.h" /* current_panel */
+#include "src/filemanager/ext.h" /* regex_command_for() */
+
+#include "src/history.h"
+#include "src/file_history.h" /* show_file_history() */
+#include "src/execute.h"
+#include "src/keymap.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_remove_ext_script (WView * view)
+{
+ if (view->ext_script != NULL)
+ {
+ mc_unlink (view->ext_script);
+ vfs_path_free (view->ext_script, TRUE);
+ view->ext_script = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Both views */
+static void
+mcview_search (WView * view, gboolean start_search)
+{
+ off_t want_search_start = view->search_start;
+
+ if (start_search)
+ {
+ if (mcview_dialog_search (view))
+ {
+ if (view->mode_flags.hex)
+ want_search_start = view->hex_cursor;
+
+ mcview_do_search (view, want_search_start);
+ }
+ }
+ else
+ {
+ if (view->mode_flags.hex)
+ {
+ if (!mcview_search_options.backwards)
+ want_search_start = view->hex_cursor + 1;
+ else if (view->hex_cursor > 0)
+ want_search_start = view->hex_cursor - 1;
+ else
+ want_search_start = 0;
+ }
+
+ mcview_do_search (view, want_search_start);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_continue_search_cmd (WView * view)
+{
+ if (view->last_search_string != NULL)
+ mcview_search (view, FALSE);
+ else
+ {
+ /* find last search string in history */
+ GList *history;
+
+ history = mc_config_history_get (MC_HISTORY_SHARED_SEARCH);
+ if (history != NULL)
+ {
+ /* FIXME: is it possible that history->data == NULL? */
+ view->last_search_string = (gchar *) history->data;
+ history->data = NULL;
+ history = g_list_first (history);
+ g_list_free_full (history, g_free);
+
+ if (mcview_search_init (view))
+ {
+ mcview_search (view, FALSE);
+ return;
+ }
+
+ /* found, but cannot init search */
+ MC_PTR_FREE (view->last_search_string);
+ }
+
+ /* if not... then ask for an expression */
+ mcview_search (view, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_hook (void *v)
+{
+ WView *view = (WView *) v;
+ WPanel *panel;
+
+ /* If the user is busy typing, wait until he finishes to update the
+ screen */
+ if (!is_idle ())
+ {
+ if (!hook_present (idle_hook, mcview_hook))
+ add_hook (&idle_hook, mcview_hook, v);
+ return;
+ }
+
+ delete_hook (&idle_hook, mcview_hook);
+
+ if (get_current_type () == view_listing)
+ panel = current_panel;
+ else if (get_other_type () == view_listing)
+ panel = other_panel;
+ else
+ return;
+
+ mcview_done (view);
+ mcview_init (view);
+ mcview_load (view, 0, panel_current_entry (panel)->fname->str, 0, 0, 0);
+ mcview_display (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+mcview_handle_editkey (WView * view, int key)
+{
+ struct hexedit_change_node *node;
+ int byte_val = -1;
+
+ /* Has there been a change at this position? */
+ node = view->change_list;
+ while ((node != NULL) && (node->offset != view->hex_cursor))
+ node = node->next;
+
+ if (!view->hexview_in_text)
+ {
+ /* Hex editing */
+ unsigned int hexvalue = 0;
+
+ if (key >= '0' && key <= '9')
+ hexvalue = 0 + (key - '0');
+ else if (key >= 'A' && key <= 'F')
+ hexvalue = 10 + (key - 'A');
+ else if (key >= 'a' && key <= 'f')
+ hexvalue = 10 + (key - 'a');
+ else
+ return MSG_NOT_HANDLED;
+
+ if (node != NULL)
+ byte_val = node->value;
+ else
+ mcview_get_byte (view, view->hex_cursor, &byte_val);
+
+ if (view->hexedit_lownibble)
+ byte_val = (byte_val & 0xf0) | (hexvalue);
+ else
+ byte_val = (byte_val & 0x0f) | (hexvalue << 4);
+ }
+ else
+ {
+ /* Text editing */
+ if (key < 256 && key != '\t')
+ byte_val = key;
+ else
+ return MSG_NOT_HANDLED;
+ }
+
+ if ((view->filename_vpath != NULL)
+ && (*(vfs_path_get_last_path_str (view->filename_vpath)) != '\0')
+ && (view->change_list == NULL))
+ view->locked = lock_file (view->filename_vpath);
+
+ if (node == NULL)
+ {
+ node = g_new (struct hexedit_change_node, 1);
+ node->offset = view->hex_cursor;
+ node->value = byte_val;
+ mcview_enqueue_change (&view->change_list, node);
+ }
+ else
+ node->value = byte_val;
+
+ view->dirty++;
+ mcview_move_right (view, 1);
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_load_next_prev_init (WView * view)
+{
+ if (mc_global.mc_run_mode != MC_RUN_VIEWER)
+ {
+ /* get file list from current panel. Update it each time */
+ view->dir = &current_panel->dir;
+ view->dir_idx = &current_panel->current;
+ }
+ else if (view->dir == NULL)
+ {
+ /* Run from command line */
+ /* Run 1st time. Load/get directory */
+
+ /* TODO: check mtime of directory to reload it */
+
+ dir_sort_options_t sort_op = { FALSE, TRUE, FALSE };
+
+ /* load directory where requested file is */
+ view->dir = g_new0 (dir_list, 1);
+ view->dir_idx = g_new (int, 1);
+
+ if (dir_list_load
+ (view->dir, view->workdir_vpath, (GCompareFunc) sort_name, &sort_op, NULL))
+ {
+ const char *fname;
+ size_t fname_len;
+ int i;
+
+ fname = x_basename (vfs_path_as_str (view->filename_vpath));
+ fname_len = strlen (fname);
+
+ /* search current file in the list */
+ for (i = 0; i != view->dir->len; i++)
+ {
+ const file_entry_t *fe = &view->dir->list[i];
+
+ if (fname_len == fe->fname->len && strncmp (fname, fe->fname->str, fname_len) == 0)
+ break;
+ }
+
+ *view->dir_idx = i;
+ }
+ else
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
+ MC_PTR_FREE (view->dir);
+ MC_PTR_FREE (view->dir_idx);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_scan_for_file (WView * view, int direction)
+{
+ int i;
+
+ for (i = *view->dir_idx + direction; i != *view->dir_idx; i += direction)
+ {
+ if (i < 0)
+ i = view->dir->len - 1;
+ if (i == view->dir->len)
+ i = 0;
+ if (!S_ISDIR (view->dir->list[i].st.st_mode))
+ break;
+ }
+
+ *view->dir_idx = i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_load_next_prev (WView * view, int direction)
+{
+ dir_list *dir;
+ int *dir_idx;
+ vfs_path_t *vfile;
+ vfs_path_t *ext_script = NULL;
+
+ mcview_load_next_prev_init (view);
+ mcview_scan_for_file (view, direction);
+
+ /* reinit view */
+ dir = view->dir;
+ dir_idx = view->dir_idx;
+ view->dir = NULL;
+ view->dir_idx = NULL;
+ vfile =
+ vfs_path_append_new (view->workdir_vpath, dir->list[*dir_idx].fname->str, (char *) NULL);
+ mcview_done (view);
+ mcview_remove_ext_script (view);
+ mcview_init (view);
+ if (regex_command_for (view, vfile, "View", &ext_script) == 0)
+ mcview_load (view, NULL, vfs_path_as_str (vfile), 0, 0, 0);
+ vfs_path_free (vfile, TRUE);
+ view->dir = dir;
+ view->dir_idx = dir_idx;
+ view->ext_script = ext_script;
+
+ view->dpy_bbar_dirty = FALSE; /* FIXME */
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_load_file_from_history (WView * view)
+{
+ char *filename;
+ int action;
+
+ filename = show_file_history (CONST_WIDGET (view), &action);
+
+ if (filename != NULL && (action == CK_View || action == CK_Enter))
+ {
+ mcview_done (view);
+ mcview_init (view);
+
+ mcview_load (view, NULL, filename, 0, 0, 0);
+
+ view->dpy_bbar_dirty = FALSE; /* FIXME */
+ view->dirty++;
+ }
+
+ g_free (filename);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+mcview_execute_cmd (WView * view, long command)
+{
+ int res = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_Help:
+ {
+ ev_help_t event_data = { NULL, "[Internal File Viewer]" };
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+ }
+ break;
+ case CK_HexMode:
+ /* Toggle between hex view and text view */
+ mcview_toggle_hex_mode (view);
+ break;
+ case CK_HexEditMode:
+ /* Toggle between hexview and hexedit mode */
+ mcview_toggle_hexedit_mode (view);
+ break;
+ case CK_ToggleNavigation:
+ view->hexview_in_text = !view->hexview_in_text;
+ view->dirty++;
+ break;
+ case CK_LeftQuick:
+ if (!view->mode_flags.hex)
+ mcview_move_left (view, 10);
+ break;
+ case CK_RightQuick:
+ if (!view->mode_flags.hex)
+ mcview_move_right (view, 10);
+ break;
+ case CK_Goto:
+ {
+ off_t addr;
+
+ if (mcview_dialog_goto (view, &addr))
+ {
+ if (addr >= 0)
+ mcview_moveto_offset (view, addr);
+ else
+ {
+ message (D_ERROR, _("Warning"), "%s", _("Invalid value"));
+ view->dirty++;
+ }
+ }
+ break;
+ }
+ case CK_Save:
+ mcview_hexedit_save_changes (view);
+ break;
+ case CK_Search:
+ mcview_search (view, TRUE);
+ break;
+ case CK_SearchContinue:
+ mcview_continue_search_cmd (view);
+ break;
+ case CK_SearchForward:
+ mcview_search_options.backwards = FALSE;
+ mcview_search (view, TRUE);
+ break;
+ case CK_SearchForwardContinue:
+ mcview_search_options.backwards = FALSE;
+ mcview_continue_search_cmd (view);
+ break;
+ case CK_SearchBackward:
+ mcview_search_options.backwards = TRUE;
+ mcview_search (view, TRUE);
+ break;
+ case CK_SearchBackwardContinue:
+ mcview_search_options.backwards = TRUE;
+ mcview_continue_search_cmd (view);
+ break;
+ case CK_SearchOppositeContinue:
+ {
+ gboolean direction;
+
+ direction = mcview_search_options.backwards;
+ mcview_search_options.backwards = !direction;
+ mcview_continue_search_cmd (view);
+ mcview_search_options.backwards = direction;
+ }
+ break;
+ case CK_WrapMode:
+ /* Toggle between wrapped and unwrapped view */
+ mcview_toggle_wrap_mode (view);
+ break;
+ case CK_MagicMode:
+ mcview_toggle_magic_mode (view);
+ break;
+ case CK_NroffMode:
+ mcview_toggle_nroff_mode (view);
+ break;
+ case CK_Home:
+ mcview_moveto_bol (view);
+ break;
+ case CK_End:
+ mcview_moveto_eol (view);
+ break;
+ case CK_Left:
+ mcview_move_left (view, 1);
+ break;
+ case CK_Right:
+ mcview_move_right (view, 1);
+ break;
+ case CK_Up:
+ mcview_move_up (view, 1);
+ break;
+ case CK_Down:
+ mcview_move_down (view, 1);
+ break;
+ case CK_HalfPageUp:
+ mcview_move_up (view, (view->data_area.lines + 1) / 2);
+ break;
+ case CK_HalfPageDown:
+ mcview_move_down (view, (view->data_area.lines + 1) / 2);
+ break;
+ case CK_PageUp:
+ mcview_move_up (view, view->data_area.lines);
+ break;
+ case CK_PageDown:
+ mcview_move_down (view, view->data_area.lines);
+ break;
+ case CK_Top:
+ mcview_moveto_top (view);
+ break;
+ case CK_Bottom:
+ mcview_moveto_bottom (view);
+ break;
+ case CK_Shell:
+ toggle_subshell ();
+ break;
+ case CK_Ruler:
+ mcview_display_toggle_ruler (view);
+ break;
+ case CK_Bookmark:
+ view->dpy_start = view->marks[view->marker];
+ view->dpy_paragraph_skip_lines = 0; /* TODO: remember this value in the marker? */
+ view->dpy_wrap_dirty = TRUE;
+ view->dirty++;
+ break;
+ case CK_BookmarkGoto:
+ view->marks[view->marker] = view->dpy_start;
+ break;
+#ifdef HAVE_CHARSET
+ case CK_SelectCodepage:
+ mcview_select_encoding (view);
+ view->dirty++;
+ break;
+#endif
+ case CK_FileNext:
+ case CK_FilePrev:
+ /* Does not work in panel mode */
+ if (!mcview_is_in_panel (view))
+ mcview_load_next_prev (view, command == CK_FileNext ? 1 : -1);
+ break;
+ case CK_History:
+ mcview_load_file_from_history (view);
+ break;
+ case CK_Quit:
+ if (!mcview_is_in_panel (view))
+ dlg_close (DIALOG (WIDGET (view)->owner));
+ break;
+ case CK_Cancel:
+ /* don't close viewer due to SIGINT */
+ break;
+ default:
+ res = MSG_NOT_HANDLED;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+mcview_lookup_key (WView * view, int key)
+{
+ if (view->mode_flags.hex)
+ return keybind_lookup_keymap_command (view->hex_keymap, key);
+
+ return widget_lookup_key (WIDGET (view), key);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Both views */
+static cb_ret_t
+mcview_handle_key (WView * view, int key)
+{
+ long command;
+
+#ifdef HAVE_CHARSET
+ key = convert_from_input_c (key);
+#endif
+
+ if (view->hexedit_mode && view->mode_flags.hex
+ && mcview_handle_editkey (view, key) == MSG_HANDLED)
+ return MSG_HANDLED;
+
+ command = mcview_lookup_key (view, key);
+ if (command != CK_IgnoreKey && mcview_execute_cmd (view, command) == MSG_HANDLED)
+ return MSG_HANDLED;
+
+#ifdef MC_ENABLE_DEBUGGING_CODE
+ if (key == 't')
+ { /* mnemonic: "test" */
+ mcview_ccache_dump (view);
+ return MSG_HANDLED;
+ }
+#endif
+ if (key >= '0' && key <= '9')
+ view->marker = key - '0';
+
+ /* Key not used */
+ return MSG_NOT_HANDLED;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mcview_resize (WView * view)
+{
+ view->dpy_wrap_dirty = TRUE;
+ mcview_compute_areas (view);
+ mcview_update_bytes_per_line (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_ok_to_quit (WView * view)
+{
+ int r;
+
+ if (view->change_list == NULL)
+ return TRUE;
+
+ if (!mc_global.midnight_shutdown)
+ {
+ query_set_sel (2);
+ r = query_dialog (_("Quit"),
+ _("File was modified. Save with exit?"), D_NORMAL, 3,
+ _("&Yes"), _("&No"), _("&Cancel quit"));
+ }
+ else
+ {
+ r = query_dialog (_("Quit"),
+ _("Midnight Commander is being shut down.\nSave modified file?"),
+ D_NORMAL, 2, _("&Yes"), _("&No"));
+ /* Esc is No */
+ if (r == -1)
+ r = 1;
+ }
+
+ switch (r)
+ {
+ case 0: /* Yes */
+ return mcview_hexedit_save_changes (view) || mc_global.midnight_shutdown;
+ case 1: /* No */
+ mcview_hexedit_free_change_list (view);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+mcview_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WView *view = (WView *) w;
+ cb_ret_t i;
+
+ mcview_compute_areas (view);
+ mcview_update_bytes_per_line (view);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ if (mcview_is_in_panel (view))
+ add_hook (&select_file_hook, mcview_hook, view);
+ else
+ view->dpy_bbar_dirty = TRUE;
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ mcview_display (view);
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ if (view->mode_flags.hex)
+ mcview_place_cursor (view);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ i = mcview_handle_key (view, parm);
+ mcview_update (view);
+ return i;
+
+ case MSG_ACTION:
+ i = mcview_execute_cmd (view, parm);
+ mcview_update (view);
+ return i;
+
+ case MSG_FOCUS:
+ view->dpy_bbar_dirty = TRUE;
+ /* TODO: get rid of draw here before MSG_DRAW */
+ mcview_update (view);
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ widget_default_callback (w, NULL, MSG_RESIZE, 0, data);
+ mcview_resize (view);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ if (mcview_is_in_panel (view))
+ {
+ delete_hook (&select_file_hook, mcview_hook);
+
+ /*
+ * In some cases when mc startup is very slow and one panel is in quick view mode,
+ * @view is registered in two hook lists at the same time:
+ * mcview_callback (MSG_INIT) -> add_hook (&select_file_hook)
+ * mcview_hook () -> add_hook (&idle_hook).
+ * If initialization of file manager is not completed yet, but user switches
+ * panel mode from qick view to another one (by pressing C-x q), the following
+ * occurs:
+ * view hook is deleted from select_file_hook list via following call chain:
+ * create_panel (view_listing) -> widget_replace () ->
+ * send_message (MSG_DESTROY) -> mcview_callback (MSG_DESTROY) ->
+ * delete_hook (&select_file_hook);
+ * @view object is free'd:
+ * create_panel (view_listing) -> g_free (old_widget);
+ * but @view still is in idle_hook list and tried to be executed:
+ * frontend_dlg_run () -> execute_hooks (idle_hook).
+ * Thus here we have access to free'd @view object. To prevent this, remove view hook
+ * from idle_hook list.
+ */
+ delete_hook (&idle_hook, mcview_hook);
+
+ if (mc_global.midnight_shutdown)
+ mcview_ok_to_quit (view);
+ }
+ mcview_done (view);
+ mcview_remove_ext_script (view);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+mcview_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+ WView *view;
+
+ switch (msg)
+ {
+ case MSG_ACTION:
+ /* Handle shortcuts. */
+
+ /* Note: the buttonbar sends messages directly to the the WView, not to
+ * here, which is why we can pass NULL in the following call. */
+ return mcview_execute_cmd (NULL, parm);
+
+ case MSG_VALIDATE:
+ view = (WView *) widget_find_by_type (w, mcview_callback);
+ /* don't stop the dialog before final decision */
+ widget_set_state (w, WST_ACTIVE, TRUE);
+ if (mcview_ok_to_quit (view))
+ dlg_close (h);
+ else
+ mcview_update (view);
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/ascii.c b/src/viewer/ascii.c
new file mode 100644
index 0000000..f786dcc
--- /dev/null
+++ b/src/viewer/ascii.c
@@ -0,0 +1,1051 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for plain view
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+ Rewritten almost from scratch by:
+ Egmont Koblinger <egmont@gmail.com>, 2014
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ ------------------------------------------------------------------------------------------------
+
+ The viewer is implemented along the following design principles:
+
+ Goals: Always display simple scripts, double wide (CJK), combining accents and spacing marks
+ (often used e.g. in Devanagari) perfectly. Make the arrow keys always work correctly.
+
+ Absolutely non-goal: RTL.
+
+ Terminology:
+
+ - A "paragraph" is the text between two adjacent newline characters. A "line" or "row" is a
+ visual row on the screen. In wrap mode, the viewer formats a paragraph into one or more lines.
+
+ - The Unicode glossary <http://www.unicode.org/glossary/> doesn't seem to have a notion of "base
+ character followed by zero or more combining characters". The closest matches are "Combining
+ Character Sequence" meaning a base character followed by one or more combining characters, or
+ "Grapheme" which seems to exclude non-printable characters such as newline. In this file,
+ "combining character sequence" (or any obvious abbreviation thereof) means a base character
+ followed by zero or more (up to a current limit of 4) combining characters.
+
+ ------------------------------------------------------------------------------------------------
+
+ The parser-formatter is designed to be stateless across paragraphs. This is so that we can walk
+ backwards without having to reparse the whole file (although we still need to reparse and
+ reformat the whole paragraph, but it's a lot better). This principle needs to be changed if we
+ ever get to address tickets 1849/2977, but then we can still store (for efficiency) the parser
+ state at the beginning of the paragraph, and safely walk backwards if we don't cross an escape
+ character.
+
+ The parser-formatter, however, definitely needs to carry a state across lines. Currently this
+ state contains:
+
+ - The logical column (as if we didn't wrap). This is used for handling TAB characters after a
+ wordwrap consistently with less.
+
+ - Whether the last nroff character was bold or underlined. This is used for displaying the
+ ambiguous _\b_ sequence consistently with less.
+
+ - Whether the desired way of displaying a lonely combining accent or spacing mark is to place it
+ over a dotted circle (we do this at the beginning of the paragraph of after a TAB), or to ignore
+ the combining char and show replacement char for the spacing mark (we do this if e.g. too many
+ of these were encountered and hence we don't glue them with their base character).
+
+ - (This state needs to be expanded if e.g. we decide to print verbose replacement characters
+ (e.g. "<U+0080>") and allow these to wrap around lines.)
+
+ The state also contains the file offset, as it doesn't make sense to ever know the state without
+ knowing the corresponding offset.
+
+ The state depends on various settings (viewer width, encoding, nroff mode, charwrap or wordwrap
+ mode (if we'll have that one day) etc.), needs to be recomputed if any of these changes.
+
+ Walking forwards is usually relatively easy both in the file and on the screen. Walking
+ backwards within a paragraph would only be possible in some special cases and even then it would
+ be painful, so we always walk back to the beginning of the paragraph and reparse-reformat from
+ there.
+
+ (Walking back within a line in the file would have at least the following difficulties: handling
+ the parser state; processing invalid UTF-8; processing invalid nroff (e.g. what is "_\bA\bA"?).
+ Walking back on the display: we wouldn't know where to display the last line of a paragraph, or
+ where to display a line if its following line starts with a wide (CJK or Tab) character. Long
+ story short: just forget this approach.)
+
+ Most important variables:
+
+ - dpy_start: Both in unwrap and wrap modes this points to the beginning of the topmost displayed
+ paragraph.
+
+ - dpy_text_column: Only in unwrap mode, an additional horizontal scroll.
+
+ - dpy_paragraph_skip_lines: Only in wrap mode, an additional vertical scroll (the number of
+ lines that are scrolled off at the top from the topmost paragraph).
+
+ - dpy_state_top: Only in wrap mode, the offset and parser-formatter state at the line where
+ displaying the file begins is cached here.
+
+ - dpy_wrap_dirty: If some parameter has changed that makes it necessary to reparse-redisplay the
+ topmost paragraph.
+
+ In wrap mode, the three variables "dpy_start", "dpy_paragraph_skip_lines" and "dpy_state_top"
+ are kept consistent. Think of the first two as the ones describing the position, and the third
+ as a cached value for better performance so that we don't need to wrap the invisible beginning
+ of the topmost paragraph over and over again. The third value needs to be recomputed each time a
+ parameter that influences parsing or displaying the file (e.g. width of screen, encoding, nroff
+ mode) changes, this is signaled by "dpy_wrap_dirty" to force recomputing "dpy_state_top" (and
+ clamp "dpy_paragraph_skip_lines" if necessary).
+
+ ------------------------------------------------------------------------------------------------
+
+ Help integration
+
+ I'm planning to port the help viewer to this codebase.
+
+ Splitting at sections would still happen in the help viewer. It would either copy a section, or
+ set force_max and a similar force_min to limit displaying to one section only.
+
+ Parsing the help format would go next to the nroff parser. The colors, alternate character set,
+ and emitting the version number would go to the "state". (The version number would be
+ implemented by emitting remaining characters of a buffer in the "state" one by one, without
+ advancing in the file position.)
+
+ The active link would be drawn similarly to the search highlight. Other than that, the viewer
+ wouldn't care about links (except for their color). help.c would keep track of which one is
+ highlighted, how to advance to the next/prev on an arrow, how the scroll offset needs to be
+ adjusted when moving, etc.
+
+ Add wrapping at word boundaries to where wrapping at char boundaries happens now.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/util.h" /* is_printable() */
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/setup.h" /* option_tab_spacing */
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* The Unicode standard recommends that lonely combining characters are printed over a dotted
+ * circle. If the terminal is not UTF-8, this will be replaced by a dot anyway. */
+#define BASE_CHARACTER_FOR_LONELY_COMBINING 0x25CC /* dotted circle */
+#define MAX_COMBINING_CHARS 4 /* both slang and ncurses support exactly 4 */
+
+/* I think anything other than space (e.g. arrows) just introduce visual clutter without actually
+ * adding value. */
+#define PARTIAL_CJK_AT_LEFT_MARGIN ' '
+#define PARTIAL_CJK_AT_RIGHT_MARGIN ' '
+
+/*
+ * Wrap mode: This is for safety so that jumping to the end of file (which already includes
+ * scrolling back by a page) and then walking backwards is reasonably fast, even if the file is
+ * extremely large and consists of maybe full zeros or something like that. If there's no newline
+ * found within this limit, just start displaying from there and see what happens. We might get
+ * some displaying parameters (most importantly the columns) incorrect, but at least will show the
+ * file without spinning the CPU for ages. When scrolling back to that point, the user might see a
+ * garbled first line (even starting with an invalid partial UTF-8), but then walking back by yet
+ * another line should fix it.
+ *
+ * Unwrap mode: This is not used, we wouldn't be able to do anything reasonable without walking
+ * back a whole paragraph (well, view->data_area.height paragraphs actually).
+ */
+#define MAX_BACKWARDS_WALK_IN_PARAGRAPH (100 * 1000)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* TODO: These methods shouldn't be necessary, see ticket 3257 */
+
+static int
+mcview_wcwidth (const WView * view, int c)
+{
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ {
+ if (g_unichar_iswide (c))
+ return 2;
+ if (g_unichar_iszerowidth (c))
+ return 0;
+ }
+#else
+ (void) view;
+ (void) c;
+#endif /* HAVE_CHARSET */
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_ismark (const WView * view, int c)
+{
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ return g_unichar_ismark (c);
+#else
+ (void) view;
+ (void) c;
+#endif /* HAVE_CHARSET */
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* actually is_non_spacing_mark_or_enclosing_mark */
+static gboolean
+mcview_is_non_spacing_mark (const WView * view, int c)
+{
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ {
+ GUnicodeType type;
+
+ type = g_unichar_type (c);
+
+ return type == G_UNICODE_NON_SPACING_MARK || type == G_UNICODE_ENCLOSING_MARK;
+ }
+#else
+ (void) view;
+ (void) c;
+#endif /* HAVE_CHARSET */
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+mcview_is_spacing_mark (const WView * view, int c)
+{
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ return g_unichar_type (c) == G_UNICODE_SPACING_MARK;
+#else
+ (void) view;
+ (void) c;
+#endif /* HAVE_CHARSET */
+ return FALSE;
+}
+#endif /* 0 */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_isprint (const WView * view, int c)
+{
+#ifdef HAVE_CHARSET
+ if (!view->utf8)
+ c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
+ return g_unichar_isprint (c);
+#else
+ (void) view;
+ /* TODO this is very-very buggy by design: ticket 3257 comments 0-1 */
+ return is_printable (c);
+#endif /* HAVE_CHARSET */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mcview_char_display (const WView * view, int c, char *s)
+{
+#ifdef HAVE_CHARSET
+ if (mc_global.utf8_display)
+ {
+ if (!view->utf8)
+ c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
+ if (!g_unichar_isprint (c))
+ c = '.';
+ return g_unichar_to_utf8 (c, s);
+ }
+ if (view->utf8)
+ {
+ if (g_unichar_iswide (c))
+ {
+ s[0] = s[1] = '.';
+ return 2;
+ }
+ if (g_unichar_iszerowidth (c))
+ return 0;
+ /* TODO the is_printable check below will be broken for this */
+ c = convert_from_utf_to_current_c (c, view->converter);
+ }
+ else
+ {
+ /* TODO the is_printable check below will be broken for this */
+ c = convert_to_display_c (c);
+ }
+#else
+ (void) view;
+#endif /* HAVE_CHARSET */
+ /* TODO this is very-very buggy by design: ticket 3257 comments 0-1 */
+ if (!is_printable (c))
+ c = '.';
+ *s = c;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Just for convenience, a common interface in front of mcview_get_utf and mcview_get_byte, so that
+ * the caller doesn't have to care about utf8 vs 8-bit modes.
+ *
+ * Normally: stores c, updates state, returns TRUE.
+ * At EOF: state is unchanged, c is undefined, returns FALSE.
+ *
+ * Just as with mcview_get_utf(), invalid UTF-8 is reported using negative integers.
+ *
+ * Also, temporary hack: handle force_max here.
+ * TODO: move it to lower layers (datasource.c)?
+ */
+static gboolean
+mcview_get_next_char (WView * view, mcview_state_machine_t * state, int *c)
+{
+ /* Pretend EOF if we reached force_max */
+ if (view->force_max >= 0 && state->offset >= view->force_max)
+ return FALSE;
+
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ {
+ int char_length = 0;
+
+ if (!mcview_get_utf (view, state->offset, c, &char_length))
+ return FALSE;
+ /* Pretend EOF if we crossed force_max */
+ if (view->force_max >= 0 && state->offset + char_length > view->force_max)
+ return FALSE;
+
+ state->offset += char_length;
+ return TRUE;
+ }
+#endif /* HAVE_CHARSET */
+ if (!mcview_get_byte (view, state->offset, c))
+ return FALSE;
+ state->offset++;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * This function parses the next nroff character and gives it to you along with its desired color,
+ * so you never have to care about nroff again.
+ *
+ * The nroff mode does the backspace trick for every single character (Unicode codepoint). At least
+ * that's what the GNU groff 1.22 package produces, and that's what less 458 expects. For
+ * double-wide characters (CJK), still only a single backspace is emitted. For combining accents
+ * and such, the print-backspace-print step is repeated for the base character and then for each
+ * accent separately.
+ *
+ * So, the right place for this layer is after the bytes are interpreted in UTF-8, but before
+ * joining a base character with its combining accents.
+ *
+ * Normally: stores c and color, updates state, returns TRUE.
+ * At EOF: state is unchanged, c and color are undefined, returns FALSE.
+ *
+ * color can be null if the caller doesn't care.
+ */
+static gboolean
+mcview_get_next_maybe_nroff_char (WView * view, mcview_state_machine_t * state, int *c, int *color)
+{
+ mcview_state_machine_t state_after_nroff;
+ int c2, c3;
+
+ if (color != NULL)
+ *color = VIEW_NORMAL_COLOR;
+
+ if (!view->mode_flags.nroff)
+ return mcview_get_next_char (view, state, c);
+
+ if (!mcview_get_next_char (view, state, c))
+ return FALSE;
+ /* Don't allow nroff formatting around CR, LF, TAB or other special chars */
+ if (!mcview_isprint (view, *c))
+ return TRUE;
+
+ state_after_nroff = *state;
+
+ if (!mcview_get_next_char (view, &state_after_nroff, &c2))
+ return TRUE;
+ if (c2 != '\b')
+ return TRUE;
+
+ if (!mcview_get_next_char (view, &state_after_nroff, &c3))
+ return TRUE;
+ if (!mcview_isprint (view, c3))
+ return TRUE;
+
+ if (*c == '_' && c3 == '_')
+ {
+ *state = state_after_nroff;
+ if (color != NULL)
+ *color =
+ state->nroff_underscore_is_underlined ? VIEW_UNDERLINED_COLOR : VIEW_BOLD_COLOR;
+ }
+ else if (*c == c3)
+ {
+ *state = state_after_nroff;
+ state->nroff_underscore_is_underlined = FALSE;
+ if (color != NULL)
+ *color = VIEW_BOLD_COLOR;
+ }
+ else if (*c == '_')
+ {
+ *c = c3;
+ *state = state_after_nroff;
+ state->nroff_underscore_is_underlined = TRUE;
+ if (color != NULL)
+ *color = VIEW_UNDERLINED_COLOR;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get one base character, along with its combining or spacing mark characters.
+ *
+ * (A spacing mark is a character that extends the base character's width 1 into a combined
+ * character of width 2, yet these two character cells should not be separated. E.g. Devanagari
+ * <U+0939><U+094B>.)
+ *
+ * This method exists mainly for two reasons. One is to be able to tell if we fit on the current
+ * line or need to wrap to the next one. The other is that both slang and ncurses seem to require
+ * that the character and its combining marks are printed in a single call (or is it just a
+ * limitation of mc's wrapper to them?).
+ *
+ * For convenience, this method takes care of converting CR or CR+LF into LF.
+ * TODO this should probably happen later, when displaying the file?
+ *
+ * Normally: stores cs and color, updates state, returns >= 1 (entries in cs).
+ * At EOF: state is unchanged, cs and color are undefined, returns 0.
+ *
+ * @param view ...
+ * @param state the parser-formatter state machine's state, updated
+ * @param cs store the characters here
+ * @param clen the room available in cs (that is, at most clen-1 combining marks are allowed), must
+ * be at least 2
+ * @param color if non-NULL, store the color here, taken from the first codepoint's color
+ * @return the number of entries placed in cs, or 0 on EOF
+ */
+static int
+mcview_next_combining_char_sequence (WView * view, mcview_state_machine_t * state, int *cs,
+ int clen, int *color)
+{
+ int i = 1;
+
+ if (!mcview_get_next_maybe_nroff_char (view, state, cs, color))
+ return 0;
+
+ /* Process \r and \r\n newlines. */
+ if (cs[0] == '\r')
+ {
+ int cnext;
+
+ mcview_state_machine_t state_after_crlf = *state;
+ if (mcview_get_next_maybe_nroff_char (view, &state_after_crlf, &cnext, NULL)
+ && cnext == '\n')
+ *state = state_after_crlf;
+ cs[0] = '\n';
+ return 1;
+ }
+
+ /* We don't want combining over non-printable characters. This includes '\n' and '\t' too. */
+ if (!mcview_isprint (view, cs[0]))
+ return 1;
+
+ if (mcview_ismark (view, cs[0]))
+ {
+ if (!state->print_lonely_combining)
+ {
+ /* First character is combining. Either just return it, ... */
+ return 1;
+ }
+ else
+ {
+ /* or place this (and subsequent combining ones) over a dotted circle. */
+ cs[1] = cs[0];
+ cs[0] = BASE_CHARACTER_FOR_LONELY_COMBINING;
+ i = 2;
+ }
+ }
+
+ if (mcview_wcwidth (view, cs[0]) == 2)
+ {
+ /* Don't allow combining or spacing mark for wide characters, is this okay? */
+ return 1;
+ }
+
+ /* Look for more combining chars. Either at most clen-1 zero-width combining chars,
+ * or at most 1 spacing mark. Is this logic correct? */
+ for (; i < clen; i++)
+ {
+ mcview_state_machine_t state_after_combining;
+
+ state_after_combining = *state;
+ if (!mcview_get_next_maybe_nroff_char (view, &state_after_combining, &cs[i], NULL))
+ return i;
+ if (!mcview_ismark (view, cs[i]) || !mcview_isprint (view, cs[i]))
+ return i;
+ if (g_unichar_type (cs[i]) == G_UNICODE_SPACING_MARK)
+ {
+ /* Only allow as the first combining char. Stop processing in either case. */
+ if (i == 1)
+ {
+ *state = state_after_combining;
+ i++;
+ }
+ return i;
+ }
+ *state = state_after_combining;
+ }
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Parse, format and possibly display one visual line of text.
+ *
+ * Formatting starts at the given "state" (which encodes the file offset and parser and formatter's
+ * internal state). In unwrap mode, this should point to the beginning of the paragraph with the
+ * default state, the additional horizontal scrolling is added here. In wrap mode, this should
+ * point to the beginning of the line, with the proper state at that point.
+ *
+ * In wrap mode, if a line ends in a newline, it is consumed, even if it's exactly at the right
+ * edge. In unwrap mode, the whole remaining line, including the newline is consumed. Displaying
+ * the next line should start at "state"'s new value, or if we displayed the bottom line then
+ * state->offset tells the file offset to be shown in the top bar.
+ *
+ * If "row" is offscreen, don't actually display the line but still update "state" and return the
+ * proper value. This is used by mcview_wrap_move_down to advance in the file.
+ *
+ * @param view ...
+ * @param state the parser-formatter state machine's state, updated
+ * @param row print to this row
+ * @param paragraph_ended store TRUE if paragraph ended by newline or EOF, FALSE if wraps to next
+ * line
+ * @param linewidth store the width of the line here
+ * @return the number of rows, that is, 0 if we were already at EOF, otherwise 1
+ */
+static int
+mcview_display_line (WView * view, mcview_state_machine_t * state, int row,
+ gboolean * paragraph_ended, off_t * linewidth)
+{
+ const WRect *r = &view->data_area;
+ off_t dpy_text_column = view->mode_flags.wrap ? 0 : view->dpy_text_column;
+ int col = 0;
+ int cs[1 + MAX_COMBINING_CHARS];
+ char str[(1 + MAX_COMBINING_CHARS) * UTF8_CHAR_LEN + 1];
+ int i, j;
+
+ if (paragraph_ended != NULL)
+ *paragraph_ended = TRUE;
+
+ if (!view->mode_flags.wrap && (row < 0 || row >= r->lines) && linewidth == NULL)
+ {
+ /* Optimization: Fast forward to the end of the line, rather than carefully
+ * parsing and then not actually displaying it. */
+ off_t eol;
+ int retval;
+
+ eol = mcview_eol (view, state->offset);
+ retval = (eol > state->offset) ? 1 : 0;
+
+ mcview_state_machine_init (state, eol);
+ return retval;
+ }
+
+ while (TRUE)
+ {
+ int charwidth = 0;
+ mcview_state_machine_t state_saved;
+ int n;
+ int color;
+
+ state_saved = *state;
+ n = mcview_next_combining_char_sequence (view, state, cs, 1 + MAX_COMBINING_CHARS, &color);
+ if (n == 0)
+ {
+ if (linewidth != NULL)
+ *linewidth = col;
+ return (col > 0) ? 1 : 0;
+ }
+
+ if (view->search_start <= state->offset && state->offset < view->search_end)
+ color = VIEW_SELECTED_COLOR;
+
+ if (cs[0] == '\n')
+ {
+ /* New line: reset all formatting state for the next paragraph. */
+ mcview_state_machine_init (state, state->offset);
+ if (linewidth != NULL)
+ *linewidth = col;
+ return 1;
+ }
+
+ if (mcview_is_non_spacing_mark (view, cs[0]))
+ {
+ /* Lonely combining character. Probably leftover after too many combining chars. Just ignore. */
+ continue;
+ }
+
+ /* Nonprintable, or lonely spacing mark */
+ if ((!mcview_isprint (view, cs[0]) || mcview_ismark (view, cs[0])) && cs[0] != '\t')
+ cs[0] = '.';
+
+ for (i = 0; i < n; i++)
+ charwidth += mcview_wcwidth (view, cs[i]);
+
+ /* Adjust the width for TAB. It's handled below along with the normal characters,
+ * so that it's wrapped consistently with them, and is painted with the proper
+ * attributes (although currently it can't have a special color). */
+ if (cs[0] == '\t')
+ {
+ charwidth = option_tab_spacing - state->unwrapped_column % option_tab_spacing;
+ state->print_lonely_combining = TRUE;
+ }
+ else
+ state->print_lonely_combining = FALSE;
+
+ /* In wrap mode only: We're done with this row if the character sequence wouldn't fit.
+ * Except if at the first column, because then it wouldn't fit in the next row either.
+ * In this extreme case let the unwrapped code below do its best to display it. */
+ if (view->mode_flags.wrap && (off_t) col + charwidth > dpy_text_column + (off_t) r->cols
+ && col > 0)
+ {
+ *state = state_saved;
+ if (paragraph_ended != NULL)
+ *paragraph_ended = FALSE;
+ if (linewidth != NULL)
+ *linewidth = col;
+ return 1;
+ }
+
+ /* Display, unless outside of the viewport. */
+ if (row >= 0 && row < r->lines)
+ {
+ if ((off_t) col >= dpy_text_column &&
+ (off_t) col + charwidth <= dpy_text_column + (off_t) r->cols)
+ {
+ /* The combining character sequence fits entirely in the viewport. Print it. */
+ tty_setcolor (color);
+ widget_gotoyx (view, r->y + row, r->x + ((off_t) col - dpy_text_column));
+ if (cs[0] == '\t')
+ {
+ for (i = 0; i < charwidth; i++)
+ tty_print_char (' ');
+ }
+ else
+ {
+ j = 0;
+ for (i = 0; i < n; i++)
+ j += mcview_char_display (view, cs[i], str + j);
+ str[j] = '\0';
+ /* This is probably a bug in our tty layer, but tty_print_string
+ * normalizes the string, whereas tty_printf doesn't. Don't normalize,
+ * since we handle combining characters ourselves correctly, it's
+ * better if they are copy-pasted correctly. Ticket 3255. */
+ tty_printf ("%s", str);
+ }
+ }
+ else if ((off_t) col < dpy_text_column && (off_t) col + charwidth > dpy_text_column)
+ {
+ /* The combining character sequence would cross the left edge of the viewport.
+ * This cannot happen with wrap mode. Print replacement character(s),
+ * or spaces with the correct attributes for partial Tabs. */
+ tty_setcolor (color);
+ for (i = dpy_text_column;
+ i < (off_t) col + charwidth && i < dpy_text_column + (off_t) r->cols; i++)
+ {
+ widget_gotoyx (view, r->y + row, r->x + (i - dpy_text_column));
+ tty_print_anychar ((cs[0] == '\t') ? ' ' : PARTIAL_CJK_AT_LEFT_MARGIN);
+ }
+ }
+ else if ((off_t) col < dpy_text_column + (off_t) r->cols &&
+ (off_t) col + charwidth > dpy_text_column + (off_t) r->cols)
+ {
+ /* The combining character sequence would cross the right edge of the viewport
+ * and we're not wrapping. Print replacement character(s),
+ * or spaces with the correct attributes for partial Tabs. */
+ tty_setcolor (color);
+ for (i = col; i < dpy_text_column + (off_t) r->cols; i++)
+ {
+ widget_gotoyx (view, r->y + row, r->x + (i - dpy_text_column));
+ tty_print_anychar ((cs[0] == '\t') ? ' ' : PARTIAL_CJK_AT_RIGHT_MARGIN);
+ }
+ }
+ }
+
+ col += charwidth;
+ state->unwrapped_column += charwidth;
+
+ if (!view->mode_flags.wrap && (off_t) col >= dpy_text_column + (off_t) r->cols
+ && linewidth == NULL)
+ {
+ /* Optimization: Fast forward to the end of the line, rather than carefully
+ * parsing and then not actually displaying it. */
+ off_t eol;
+
+ eol = mcview_eol (view, state->offset);
+ mcview_state_machine_init (state, eol);
+ return 1;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Parse, format and possibly display one paragraph (perhaps not from the beginning).
+ *
+ * Formatting starts at the given "state" (which encodes the file offset and parser and formatter's
+ * internal state). In unwrap mode, this should point to the beginning of the paragraph with the
+ * default state, the additional horizontal scrolling is added here. In wrap mode, this may point
+ * to the beginning of the line within a paragraph (to display the partial paragraph at the top),
+ * with the proper state at that point.
+ *
+ * Displaying the next paragraph should start at "state"'s new value, or if we displayed the bottom
+ * line then state->offset tells the file offset to be shown in the top bar.
+ *
+ * If "row" is negative, don't display the first abs(row) lines and display the rest from the top.
+ * This was a nice idea but it's now unused :)
+ *
+ * If "row" is too large, don't display the paragraph at all but still return the number of lines.
+ * This is used when moving upwards.
+ *
+ * @param view ...
+ * @param state the parser-formatter state machine's state, updated
+ * @param row print starting at this row
+ * @return the number of rows the paragraphs is wrapped to, that is, 0 if we were already at EOF,
+ * otherwise 1 in unwrap mode, >= 1 in wrap mode. We stop when reaching the bottom of the
+ * viewport, it's not counted how many more lines the paragraph would occupy
+ */
+static int
+mcview_display_paragraph (WView * view, mcview_state_machine_t * state, int row)
+{
+ int lines = 0;
+
+ while (TRUE)
+ {
+ gboolean paragraph_ended;
+
+ lines += mcview_display_line (view, state, row, &paragraph_ended, NULL);
+ if (paragraph_ended)
+ return lines;
+
+ if (row < view->data_area.lines)
+ {
+ row++;
+ /* stop if bottom of screen reached */
+ if (row >= view->data_area.lines)
+ return lines;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Recompute dpy_state_top from dpy_start and dpy_paragraph_skip_lines. Clamp
+ * dpy_paragraph_skip_lines if necessary.
+ *
+ * This method should be called in wrap mode after changing one of the parsing or formatting
+ * properties (e.g. window width, encoding, nroff), or when switching to wrap mode from unwrap or
+ * hex.
+ *
+ * If we stayed within the same paragraph then try to keep the vertical offset within that
+ * paragraph as well. It might happen though that the paragraph became shorter than our desired
+ * vertical position, in that case move to its last row.
+ */
+static void
+mcview_wrap_fixup (WView * view)
+{
+ int lines = view->dpy_paragraph_skip_lines;
+
+ if (!view->dpy_wrap_dirty)
+ return;
+ view->dpy_wrap_dirty = FALSE;
+
+ view->dpy_paragraph_skip_lines = 0;
+ mcview_state_machine_init (&view->dpy_state_top, view->dpy_start);
+
+ while (lines-- != 0)
+ {
+ mcview_state_machine_t state_prev;
+ gboolean paragraph_ended;
+
+ state_prev = view->dpy_state_top;
+ if (mcview_display_line (view, &view->dpy_state_top, -1, &paragraph_ended, NULL) == 0)
+ break;
+ if (paragraph_ended)
+ {
+ view->dpy_state_top = state_prev;
+ break;
+ }
+ view->dpy_paragraph_skip_lines++;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * In both wrap and unwrap modes, dpy_start points to the beginning of the paragraph.
+ *
+ * In unwrap mode, start displaying from this position, probably applying an additional horizontal
+ * scroll.
+ *
+ * In wrap mode, an additional dpy_paragraph_skip_lines lines are skipped from the top of this
+ * paragraph. dpy_state_top contains the position and parser-formatter state corresponding to the
+ * top left corner so we can just start rendering from here. Unless dpy_wrap_dirty is set in which
+ * case dpy_state_top is invalid and we need to recompute first.
+ */
+void
+mcview_display_text (WView * view)
+{
+ const WRect *r = &view->data_area;
+ int row;
+ mcview_state_machine_t state;
+ gboolean again;
+
+ do
+ {
+ int n;
+
+ again = FALSE;
+
+ mcview_display_clean (view);
+ mcview_display_ruler (view);
+
+ if (!view->mode_flags.wrap)
+ mcview_state_machine_init (&state, view->dpy_start);
+ else
+ {
+ mcview_wrap_fixup (view);
+ state = view->dpy_state_top;
+ }
+
+ for (row = 0; row < r->lines; row += n)
+ {
+ n = mcview_display_paragraph (view, &state, row);
+ if (n == 0)
+ {
+ /* In the rare case that displaying didn't start at the beginning
+ * of the file, yet there are some empty lines at the bottom,
+ * scroll the file and display again. This happens when e.g. the
+ * window is made bigger, or the file becomes shorter due to
+ * charset change or enabling nroff. */
+ if ((view->mode_flags.wrap ? view->dpy_state_top.offset : view->dpy_start) > 0)
+ {
+ mcview_ascii_move_up (view, r->lines - row);
+ again = TRUE;
+ }
+ break;
+ }
+ }
+ }
+ while (again);
+
+ view->dpy_end = state.offset;
+ view->dpy_state_bottom = state;
+
+ tty_setcolor (VIEW_NORMAL_COLOR);
+ if (mcview_show_eof != NULL && mcview_show_eof[0] != '\0')
+ while (row < r->lines)
+ {
+ widget_gotoyx (view, r->y + row, r->x);
+ /* TODO: should make it no wider than the viewport */
+ tty_print_string (mcview_show_eof);
+ row++;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move down.
+ *
+ * It's very simple. Just invisibly format the next "lines" lines, carefully carrying the formatter
+ * state in wrap mode. But before each step we need to check if we've already hit the end of the
+ * file, in that case we can no longer move. This is done by walking from dpy_state_bottom.
+ *
+ * Note that this relies on mcview_display_text() setting dpy_state_bottom to its correct value
+ * upon rendering the screen contents. So don't call this function from other functions (e.g. at
+ * the bottom of mcview_ascii_move_up()) which invalidate this value.
+ */
+void
+mcview_ascii_move_down (WView * view, off_t lines)
+{
+ while (lines-- != 0)
+ {
+ gboolean paragraph_ended;
+
+ /* See if there's still data below the bottom line, by imaginarily displaying one
+ * more line. This takes care of reading more data into growbuf, if required.
+ * If the end position didn't advance, we're at EOF and hence bail out. */
+ if (mcview_display_line (view, &view->dpy_state_bottom, -1, &paragraph_ended, NULL) == 0)
+ break;
+
+ /* Okay, there's enough data. Move by 1 row at the top, too. No need to check for
+ * EOF, that can't happen. */
+ if (!view->mode_flags.wrap)
+ {
+ view->dpy_start = mcview_eol (view, view->dpy_start);
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ else
+ {
+ mcview_display_line (view, &view->dpy_state_top, -1, &paragraph_ended, NULL);
+ if (!paragraph_ended)
+ view->dpy_paragraph_skip_lines++;
+ else
+ {
+ view->dpy_start = view->dpy_state_top.offset;
+ view->dpy_paragraph_skip_lines = 0;
+ }
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move up.
+ *
+ * Unwrap mode: Piece of cake. Wrap mode: If we'd walk back more than the current line offset
+ * within the paragraph, we need to jump back to the previous paragraph and compute its height to
+ * see if we start from that paragraph, and repeat this if necessary. Once we're within the desired
+ * paragraph, we still need to format it from its beginning to know the state.
+ *
+ * See the top of this file for comments about MAX_BACKWARDS_WALK_IN_PARAGRAPH.
+ *
+ * force_max is a nice protection against the rare extreme case that the file underneath us
+ * changes, we don't want to endlessly consume a file of maybe full of zeros upon moving upwards.
+ */
+void
+mcview_ascii_move_up (WView * view, off_t lines)
+{
+ if (!view->mode_flags.wrap)
+ {
+ while (lines-- != 0)
+ view->dpy_start = mcview_bol (view, view->dpy_start - 1, 0);
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ else
+ {
+ int i;
+
+ while (lines > view->dpy_paragraph_skip_lines)
+ {
+ /* We need to go back to the previous paragraph. */
+ if (view->dpy_start == 0)
+ {
+ /* Oops, we're already in the first paragraph. */
+ view->dpy_paragraph_skip_lines = 0;
+ mcview_state_machine_init (&view->dpy_state_top, 0);
+ return;
+ }
+ lines -= view->dpy_paragraph_skip_lines;
+ view->force_max = view->dpy_start;
+ view->dpy_start =
+ mcview_bol (view, view->dpy_start - 1,
+ view->dpy_start - MAX_BACKWARDS_WALK_IN_PARAGRAPH);
+ mcview_state_machine_init (&view->dpy_state_top, view->dpy_start);
+ /* This is a tricky way of denoting that we're at the end of the paragraph.
+ * Normally we'd jump to the next paragraph and reset paragraph_skip_lines. But for
+ * walking backwards this is exactly what we need. */
+ view->dpy_paragraph_skip_lines =
+ mcview_display_paragraph (view, &view->dpy_state_top, view->data_area.lines);
+ view->force_max = -1;
+ }
+
+ /* Okay, we have have dpy_start pointing to the desired paragraph, and we still need to
+ * walk back "lines" lines from the current "dpy_paragraph_skip_lines" offset. We can't do
+ * that, so walk from the beginning of the paragraph. */
+ mcview_state_machine_init (&view->dpy_state_top, view->dpy_start);
+ view->dpy_paragraph_skip_lines -= lines;
+ for (i = 0; i < view->dpy_paragraph_skip_lines; i++)
+ mcview_display_line (view, &view->dpy_state_top, -1, NULL, NULL);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_ascii_moveto_bol (WView * view)
+{
+ if (!view->mode_flags.wrap)
+ view->dpy_text_column = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_ascii_moveto_eol (WView * view)
+{
+ if (!view->mode_flags.wrap)
+ {
+ mcview_state_machine_t state;
+ off_t linewidth;
+
+ /* Get the width of the topmost paragraph. */
+ mcview_state_machine_init (&state, view->dpy_start);
+ mcview_display_line (view, &state, -1, NULL, &linewidth);
+ view->dpy_text_column = DOZ (linewidth, (off_t) view->data_area.cols);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_state_machine_init (mcview_state_machine_t * state, off_t offset)
+{
+ memset (state, 0, sizeof (*state));
+ state->offset = offset;
+ state->print_lonely_combining = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/coord_cache.c b/src/viewer/coord_cache.c
new file mode 100644
index 0000000..190dbd5
--- /dev/null
+++ b/src/viewer/coord_cache.c
@@ -0,0 +1,395 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for work with coordinate cache (ccache)
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ This cache provides you with a fast lookup to map file offsets into
+ line/column pairs and vice versa. The interface to the mapping is
+ provided by the functions mcview_coord_to_offset() and
+ mcview_offset_to_coord().
+
+ The cache is implemented as a simple sorted array holding entries
+ that map some of the offsets to their line/column pair. Entries that
+ are not cached themselves are interpolated (exactly) from their
+ neighbor entries. The algorithm used for determining the line/column
+ for a specific offset needs to be kept synchronized with the one used
+ in display().
+ */
+
+#include <config.h>
+
+#include <string.h> /* memset() */
+#ifdef MC_ENABLE_DEBUGGING_CODE
+#include <inttypes.h> /* uintmax_t */
+#endif
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define VIEW_COORD_CACHE_GRANUL 1024
+#define CACHE_CAPACITY_DELTA 64
+
+#define coord_cache_index(c, i) ((coord_cache_entry_t *) g_ptr_array_index ((c), (i)))
+
+/*** file scope type declarations ****************************************************************/
+
+typedef gboolean (*cmp_func_t) (const coord_cache_entry_t * a, const coord_cache_entry_t * b);
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* insert new cache entry into the cache */
+static inline void
+mcview_ccache_add_entry (GPtrArray * cache, const coord_cache_entry_t * entry)
+{
+#if GLIB_CHECK_VERSION (2, 68, 0)
+ g_ptr_array_add (cache, g_memdup2 (entry, sizeof (*entry)));
+#else
+ g_ptr_array_add (cache, g_memdup (entry, sizeof (*entry)));
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_coord_cache_entry_less_offset (const coord_cache_entry_t * a, const coord_cache_entry_t * b)
+{
+ return (a->cc_offset < b->cc_offset);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_coord_cache_entry_less_plain (const coord_cache_entry_t * a, const coord_cache_entry_t * b)
+{
+ if (a->cc_line < b->cc_line)
+ return TRUE;
+
+ if (a->cc_line == b->cc_line)
+ return (a->cc_column < b->cc_column);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_coord_cache_entry_less_nroff (const coord_cache_entry_t * a, const coord_cache_entry_t * b)
+{
+ if (a->cc_line < b->cc_line)
+ return TRUE;
+
+ if (a->cc_line == b->cc_line)
+ return (a->cc_nroff_column < b->cc_nroff_column);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Find and return the index of the last cache entry that is
+ * smaller than ''coord'', according to the criterion ''sort_by''. */
+
+static inline size_t
+mcview_ccache_find (WView * view, const coord_cache_entry_t * coord, cmp_func_t cmp_func)
+{
+ size_t base = 0;
+ size_t limit = view->coord_cache->len;
+
+ g_assert (limit != 0);
+
+ while (limit > 1)
+ {
+ size_t i;
+
+ i = base + limit / 2;
+ if (cmp_func (coord, coord_cache_index (view->coord_cache, i)))
+ {
+ /* continue the search in the lower half of the cache */
+ ;
+ }
+ else
+ {
+ /* continue the search in the upper half of the cache */
+ base = i;
+ }
+
+ limit = (limit + 1) / 2;
+ }
+
+ return base;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef MC_ENABLE_DEBUGGING_CODE
+
+void
+mcview_ccache_dump (WView * view)
+{
+ FILE *f;
+ off_t offset, line, column, nextline_offset, filesize;
+ guint i;
+ const GPtrArray *cache = view->coord_cache;
+
+ g_assert (cache != NULL);
+
+ filesize = mcview_get_filesize (view);
+
+ f = fopen ("mcview-ccache.out", "w");
+ if (f == NULL)
+ return;
+
+ (void) setvbuf (f, NULL, _IONBF, 0);
+
+ /* cache entries */
+ for (i = 0; i < cache->len; i++)
+ {
+ coord_cache_entry_t *e;
+
+ e = coord_cache_index (cache, i);
+ (void) fprintf (f,
+ "entry %8u offset %8" PRIuMAX
+ " line %8" PRIuMAX " column %8" PRIuMAX
+ " nroff_column %8" PRIuMAX "\n",
+ (unsigned int) i,
+ (uintmax_t) e->cc_offset, (uintmax_t) e->cc_line, (uintmax_t) e->cc_column,
+ (uintmax_t) e->cc_nroff_column);
+ }
+ (void) fprintf (f, "\n");
+
+ /* offset -> line/column translation */
+ for (offset = 0; offset < filesize; offset++)
+ {
+ mcview_offset_to_coord (view, &line, &column, offset);
+ (void) fprintf (f,
+ "offset %8" PRIuMAX " line %8" PRIuMAX " column %8" PRIuMAX "\n",
+ (uintmax_t) offset, (uintmax_t) line, (uintmax_t) column);
+ }
+
+ /* line/column -> offset translation */
+ for (line = 0; TRUE; line++)
+ {
+ mcview_coord_to_offset (view, &nextline_offset, line + 1, 0);
+ (void) fprintf (f, "nextline_offset %8" PRIuMAX "\n", (uintmax_t) nextline_offset);
+
+ for (column = 0; TRUE; column++)
+ {
+ mcview_coord_to_offset (view, &offset, line, column);
+ if (offset >= nextline_offset)
+ break;
+
+ (void) fprintf (f,
+ "line %8" PRIuMAX " column %8" PRIuMAX " offset %8" PRIuMAX "\n",
+ (uintmax_t) line, (uintmax_t) column, (uintmax_t) offset);
+ }
+
+ if (nextline_offset >= filesize - 1)
+ break;
+ }
+
+ (void) fclose (f);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+/** Look up the missing components of ''coord'', which are given by
+ * ''lookup_what''. The function returns the smallest value that
+ * matches the existing components of ''coord''.
+ */
+
+void
+mcview_ccache_lookup (WView * view, coord_cache_entry_t * coord, enum ccache_type lookup_what)
+{
+ size_t i;
+ GPtrArray *cache;
+ coord_cache_entry_t current, next, entry;
+ enum ccache_type sorter;
+ off_t limit;
+ cmp_func_t cmp_func;
+
+ enum
+ {
+ NROFF_START,
+ NROFF_BACKSPACE,
+ NROFF_CONTINUATION
+ } nroff_state;
+
+ if (view->coord_cache == NULL)
+ view->coord_cache = g_ptr_array_new_full (CACHE_CAPACITY_DELTA, g_free);
+
+ cache = view->coord_cache;
+
+ if (cache->len == 0)
+ {
+ memset (&current, 0, sizeof (current));
+ mcview_ccache_add_entry (cache, &current);
+ }
+
+ sorter = (lookup_what == CCACHE_OFFSET) ? CCACHE_LINECOL : CCACHE_OFFSET;
+
+ if (sorter == CCACHE_OFFSET)
+ cmp_func = mcview_coord_cache_entry_less_offset;
+ else if (view->mode_flags.nroff)
+ cmp_func = mcview_coord_cache_entry_less_nroff;
+ else
+ cmp_func = mcview_coord_cache_entry_less_plain;
+
+ tty_enable_interrupt_key ();
+
+ retry:
+ /* find the two neighbor entries in the cache */
+ i = mcview_ccache_find (view, coord, cmp_func);
+ /* now i points to the lower neighbor in the cache */
+
+ current = *coord_cache_index (cache, i);
+ if (i + 1 < view->coord_cache->len)
+ limit = coord_cache_index (cache, i + 1)->cc_offset;
+ else
+ limit = current.cc_offset + VIEW_COORD_CACHE_GRANUL;
+
+ entry = current;
+ nroff_state = NROFF_START;
+ for (; current.cc_offset < limit; current = next)
+ {
+ int c;
+
+ if (!mcview_get_byte (view, current.cc_offset, &c))
+ break;
+
+ if (!cmp_func (&current, coord) &&
+ (lookup_what != CCACHE_OFFSET || !view->mode_flags.nroff || nroff_state == NROFF_START))
+ break;
+
+ /* Provide useful default values for 'next' */
+ next.cc_offset = current.cc_offset + 1;
+ next.cc_line = current.cc_line;
+ next.cc_column = current.cc_column + 1;
+ next.cc_nroff_column = current.cc_nroff_column + 1;
+
+ /* and override some of them as necessary. */
+ if (c == '\r')
+ {
+ int nextc = -1;
+
+ mcview_get_byte_indexed (view, current.cc_offset, 1, &nextc);
+
+ /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
+ * followed by anything else, it is a Mac line ending and
+ * produces a line break.
+ */
+ if (nextc == '\r' || nextc == '\n')
+ {
+ next.cc_column = current.cc_column;
+ next.cc_nroff_column = current.cc_nroff_column;
+ }
+ else
+ {
+ next.cc_line = current.cc_line + 1;
+ next.cc_column = 0;
+ next.cc_nroff_column = 0;
+ }
+ }
+ else if (nroff_state == NROFF_BACKSPACE)
+ next.cc_nroff_column = current.cc_nroff_column - 1;
+ else if (c == '\t')
+ {
+ next.cc_column = mcview_offset_rounddown (current.cc_column, 8) + 8;
+ next.cc_nroff_column = mcview_offset_rounddown (current.cc_nroff_column, 8) + 8;
+ }
+ else if (c == '\n')
+ {
+ next.cc_line = current.cc_line + 1;
+ next.cc_column = 0;
+ next.cc_nroff_column = 0;
+ }
+ else
+ {
+ ; /* Use all default values from above */
+ }
+
+ switch (nroff_state)
+ {
+ case NROFF_START:
+ case NROFF_CONTINUATION:
+ nroff_state = mcview_is_nroff_sequence (view, current.cc_offset)
+ ? NROFF_BACKSPACE : NROFF_START;
+ break;
+ case NROFF_BACKSPACE:
+ nroff_state = NROFF_CONTINUATION;
+ break;
+ default:
+ break;
+ }
+
+ /* Cache entries must guarantee that for each i < j,
+ * line[i] <= line[j] and column[i] < column[j]. In the case of
+ * nroff sequences and '\r' characters, this is not guaranteed,
+ * so we cannot save them. */
+ if (nroff_state == NROFF_START && c != '\r')
+ entry = next;
+ }
+
+ if (i + 1 == cache->len && entry.cc_offset != coord_cache_index (cache, i)->cc_offset)
+ {
+ mcview_ccache_add_entry (cache, &entry);
+
+ if (!tty_got_interrupt ())
+ goto retry;
+ }
+
+ tty_disable_interrupt_key ();
+
+ if (lookup_what == CCACHE_OFFSET)
+ coord->cc_offset = current.cc_offset;
+ else
+ {
+ coord->cc_line = current.cc_line;
+ coord->cc_column = current.cc_column;
+ coord->cc_nroff_column = current.cc_nroff_column;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/datasource.c b/src/viewer/datasource.c
new file mode 100644
index 0000000..ea4199c
--- /dev/null
+++ b/src/viewer/datasource.c
@@ -0,0 +1,434 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Functions for datasources
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ The data source provides the viewer with data from either a file, a
+ string or the output of a command. The mcview_get_byte() function can be
+ used to get the value of a byte at a specific offset. If the offset
+ is out of range, -1 is returned. The function mcview_get_byte_indexed(a,b)
+ returns the byte at the offset a+b, or -1 if a+b is out of range.
+
+ The mcview_set_byte() function has the effect that later calls to
+ mcview_get_byte() will return the specified byte for this offset. This
+ function is designed only for use by the hexedit component after
+ saving its changes. Inspect the source before you want to use it for
+ other purposes.
+
+ The mcview_get_filesize() function returns the current size of the
+ data source. If the growing buffer is used, this size may increase
+ later on. Use the mcview_may_still_grow() function when you want to
+ know if the size can change later.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* D_NORMAL, D_ERROR */
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_set_datasource_stdio_pipe (WView * view, mc_pipe_t * p)
+{
+ p->out.len = MC_PIPE_BUFSIZE;
+ p->out.null_term = FALSE;
+ p->err.len = MC_PIPE_BUFSIZE;
+ p->err.null_term = TRUE;
+ view->datasource = DS_STDIO_PIPE;
+ view->ds_stdio_pipe = p;
+ view->pipe_first_err_msg = TRUE;
+
+ mcview_growbuf_init (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_set_datasource_none (WView * view)
+{
+ view->datasource = DS_NONE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+mcview_get_filesize (WView * view)
+{
+ switch (view->datasource)
+ {
+ case DS_STDIO_PIPE:
+ case DS_VFS_PIPE:
+ return mcview_growbuf_filesize (view);
+ case DS_FILE:
+ return view->ds_file_filesize;
+ case DS_STRING:
+ return view->ds_string_len;
+ default:
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_update_filesize (WView * view)
+{
+ if (view->datasource == DS_FILE)
+ {
+ struct stat st;
+ if (mc_fstat (view->ds_file_fd, &st) != -1)
+ view->ds_file_filesize = st.st_size;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mcview_get_ptr_file (WView * view, off_t byte_index)
+{
+ g_assert (view->datasource == DS_FILE);
+
+ mcview_file_load_data (view, byte_index);
+ if (mcview_already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
+ return (char *) (view->ds_file_data + (byte_index - view->ds_file_offset));
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Invalid UTF-8 is reported as negative integers (one for each byte),
+ * see ticket 3783. */
+gboolean
+mcview_get_utf (WView * view, off_t byte_index, int *ch, int *ch_len)
+{
+ gchar *str = NULL;
+ int res;
+ gchar utf8buf[UTF8_CHAR_LEN + 1];
+
+ switch (view->datasource)
+ {
+ case DS_STDIO_PIPE:
+ case DS_VFS_PIPE:
+ str = mcview_get_ptr_growing_buffer (view, byte_index);
+ break;
+ case DS_FILE:
+ str = mcview_get_ptr_file (view, byte_index);
+ break;
+ case DS_STRING:
+ str = mcview_get_ptr_string (view, byte_index);
+ break;
+ case DS_NONE:
+ default:
+ break;
+ }
+
+ *ch = 0;
+
+ if (str == NULL)
+ return FALSE;
+
+ res = g_utf8_get_char_validated (str, -1);
+
+ if (res < 0)
+ {
+ /* Retry with explicit bytes to make sure it's not a buffer boundary */
+ int i;
+
+ for (i = 0; i < UTF8_CHAR_LEN; i++)
+ {
+ if (mcview_get_byte (view, byte_index + i, &res))
+ utf8buf[i] = res;
+ else
+ {
+ utf8buf[i] = '\0';
+ break;
+ }
+ }
+ utf8buf[UTF8_CHAR_LEN] = '\0';
+ str = utf8buf;
+ res = g_utf8_get_char_validated (str, -1);
+ }
+
+ if (res < 0)
+ {
+ /* Implicit conversion from signed char to signed int keeps negative values. */
+ *ch = *str;
+ *ch_len = 1;
+ }
+ else
+ {
+ gchar *next_ch = NULL;
+
+ *ch = res;
+ /* Calculate UTF-8 char length */
+ next_ch = g_utf8_next_char (str);
+ *ch_len = next_ch - str;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mcview_get_ptr_string (WView * view, off_t byte_index)
+{
+ g_assert (view->datasource == DS_STRING);
+
+ if (byte_index >= 0 && byte_index < (off_t) view->ds_string_len)
+ return (char *) (view->ds_string_data + byte_index);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_get_byte_string (WView * view, off_t byte_index, int *retval)
+{
+ char *p;
+
+ if (retval != NULL)
+ *retval = -1;
+
+ p = mcview_get_ptr_string (view, byte_index);
+ if (p == NULL)
+ return FALSE;
+
+ if (retval != NULL)
+ *retval = (unsigned char) (*p);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_get_byte_none (WView * view, off_t byte_index, int *retval)
+{
+ (void) &view;
+ (void) byte_index;
+
+ g_assert (view->datasource == DS_NONE);
+
+ if (retval != NULL)
+ *retval = -1;
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_set_byte (WView * view, off_t offset, byte b)
+{
+ (void) &b;
+
+ g_assert (offset < mcview_get_filesize (view));
+ g_assert (view->datasource == DS_FILE);
+
+ view->ds_file_datalen = 0; /* just force reloading */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*static */
+void
+mcview_file_load_data (WView * view, off_t byte_index)
+{
+ off_t blockoffset;
+ ssize_t res;
+ size_t bytes_read;
+
+ g_assert (view->datasource == DS_FILE);
+
+ if (mcview_already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
+ return;
+
+ if (byte_index >= view->ds_file_filesize)
+ return;
+
+ blockoffset = mcview_offset_rounddown (byte_index, view->ds_file_datasize);
+ if (mc_lseek (view->ds_file_fd, blockoffset, SEEK_SET) == -1)
+ goto error;
+
+ bytes_read = 0;
+ while (bytes_read < view->ds_file_datasize)
+ {
+ res =
+ mc_read (view->ds_file_fd, view->ds_file_data + bytes_read,
+ view->ds_file_datasize - bytes_read);
+ if (res == -1)
+ goto error;
+ if (res == 0)
+ break;
+ bytes_read += (size_t) res;
+ }
+ view->ds_file_offset = blockoffset;
+ if ((off_t) bytes_read > view->ds_file_filesize - view->ds_file_offset)
+ {
+ /* the file has grown in the meantime -- stick to the old size */
+ view->ds_file_datalen = view->ds_file_filesize - view->ds_file_offset;
+ }
+ else
+ {
+ view->ds_file_datalen = bytes_read;
+ }
+ return;
+
+ error:
+ view->ds_file_datalen = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_close_datasource (WView * view)
+{
+ switch (view->datasource)
+ {
+ case DS_NONE:
+ break;
+ case DS_STDIO_PIPE:
+ if (view->ds_stdio_pipe != NULL)
+ {
+ mcview_growbuf_done (view);
+ mcview_display (view);
+ }
+ mcview_growbuf_free (view);
+ break;
+ case DS_VFS_PIPE:
+ if (view->ds_vfs_pipe != -1)
+ mcview_growbuf_done (view);
+ mcview_growbuf_free (view);
+ break;
+ case DS_FILE:
+ (void) mc_close (view->ds_file_fd);
+ view->ds_file_fd = -1;
+ MC_PTR_FREE (view->ds_file_data);
+ break;
+ case DS_STRING:
+ MC_PTR_FREE (view->ds_string_data);
+ break;
+ default:
+ break;
+ }
+ view->datasource = DS_NONE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_set_datasource_file (WView * view, int fd, const struct stat *st)
+{
+ view->datasource = DS_FILE;
+ view->ds_file_fd = fd;
+ view->ds_file_filesize = st->st_size;
+ view->ds_file_offset = 0;
+ view->ds_file_data = g_malloc (4096);
+ view->ds_file_datalen = 0;
+ view->ds_file_datasize = 4096;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_load_command_output (WView * view, const char *command)
+{
+ mc_pipe_t *p;
+ GError *error = NULL;
+
+ mcview_close_datasource (view);
+
+ p = mc_popen (command, TRUE, TRUE, &error);
+ if (p == NULL)
+ {
+ mcview_display (view);
+ mcview_show_error (view, error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ /* Check if filter produced any output */
+ mcview_set_datasource_stdio_pipe (view, p);
+ if (!mcview_get_byte (view, 0, NULL))
+ {
+ mcview_close_datasource (view);
+ mcview_display (view);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_set_datasource_vfs_pipe (WView * view, int fd)
+{
+ g_assert (fd != -1);
+
+ view->datasource = DS_VFS_PIPE;
+ view->ds_vfs_pipe = fd;
+
+ mcview_growbuf_init (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_set_datasource_string (WView * view, const char *s)
+{
+ view->datasource = DS_STRING;
+ view->ds_string_len = strlen (s);
+ view->ds_string_data = (byte *) g_strndup (s, view->ds_string_len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/dialogs.c b/src/viewer/dialogs.c
new file mode 100644
index 0000000..f15c2ff
--- /dev/null
+++ b/src/viewer/dialogs.c
@@ -0,0 +1,263 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for paint dialogs
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+#include "lib/search.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/history.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_dialog_search (WView * view)
+{
+ char *exp = NULL;
+ int qd_result;
+ size_t num_of_types = 0;
+ gchar **list_of_types;
+
+ list_of_types = mc_search_get_types_strings_array (&num_of_types);
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Enter search string:"), input_label_above,
+ INPUT_LAST_TEXT, MC_HISTORY_SHARED_SEARCH, &exp,
+ NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (num_of_types, (const char **) list_of_types,
+ (int *) &mcview_search_options.type, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Cas&e sensitive"), &mcview_search_options.case_sens, NULL),
+ QUICK_CHECKBOX (N_("&Backwards"), &mcview_search_options.backwards, NULL),
+ QUICK_CHECKBOX (N_("&Whole words"), &mcview_search_options.whole_words, NULL),
+#ifdef HAVE_CHARSET
+ QUICK_CHECKBOX (N_("&All charsets"), &mcview_search_options.all_codepages, NULL),
+#endif
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 58 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Search"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ qd_result = quick_dialog (&qdlg);
+ }
+
+ g_strfreev (list_of_types);
+
+ if (qd_result == B_CANCEL || exp[0] == '\0')
+ {
+ g_free (exp);
+ return FALSE;
+ }
+
+#ifdef HAVE_CHARSET
+ {
+ GString *tmp;
+
+ tmp = str_convert_to_input (exp);
+ g_free (exp);
+ if (tmp != NULL)
+ exp = g_string_free (tmp, FALSE);
+ else
+ exp = g_strdup ("");
+ }
+#endif
+
+ mcview_search_deinit (view);
+ view->last_search_string = exp;
+
+ return mcview_search_init (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_dialog_goto (WView * view, off_t * offset)
+{
+ typedef enum
+ {
+ MC_VIEW_GOTO_LINENUM = 0,
+ MC_VIEW_GOTO_PERCENT = 1,
+ MC_VIEW_GOTO_OFFSET_DEC = 2,
+ MC_VIEW_GOTO_OFFSET_HEX = 3
+ } mcview_goto_type_t;
+
+ const char *mc_view_goto_str[] = {
+ N_("&Line number"),
+ N_("Pe&rcents"),
+ N_("&Decimal offset"),
+ N_("He&xadecimal offset")
+ };
+
+ static mcview_goto_type_t current_goto_type = MC_VIEW_GOTO_LINENUM;
+
+ size_t num_of_types;
+ char *exp = NULL;
+ int qd_result;
+ gboolean res;
+
+ num_of_types = G_N_ELEMENTS (mc_view_goto_str);
+
+#ifdef ENABLE_NLS
+ {
+ size_t i;
+
+ for (i = 0; i < num_of_types; i++)
+ mc_view_goto_str[i] = _(mc_view_goto_str[i]);
+ }
+#endif
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_INPUT (INPUT_LAST_TEXT, MC_HISTORY_VIEW_GOTO, &exp, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_RADIO (num_of_types, (const char **) mc_view_goto_str, (int *) &current_goto_type,
+ NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 40 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Goto"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ /* run dialog */
+ qd_result = quick_dialog (&qdlg);
+ }
+
+ *offset = -1;
+
+ /* check input line value */
+ res = (qd_result != B_CANCEL && exp[0] != '\0');
+ if (res)
+ {
+ int base = (current_goto_type == MC_VIEW_GOTO_OFFSET_HEX) ? 16 : 10;
+ off_t addr;
+ char *error;
+
+ addr = (off_t) g_ascii_strtoll (exp, &error, base);
+ if ((*error == '\0') && (addr >= 0))
+ {
+ switch (current_goto_type)
+ {
+ case MC_VIEW_GOTO_LINENUM:
+ /* Line number entered by user is 1-based. */
+ if (addr > 0)
+ addr--;
+ mcview_coord_to_offset (view, offset, addr, 0);
+ *offset = mcview_bol (view, *offset, 0);
+ break;
+ case MC_VIEW_GOTO_PERCENT:
+ if (addr > 100)
+ addr = 100;
+ /* read all data from pipe to get real size */
+ if (view->growbuf_in_use)
+ mcview_growbuf_read_all_data (view);
+ *offset = addr * mcview_get_filesize (view) / 100;
+ if (!view->mode_flags.hex)
+ *offset = mcview_bol (view, *offset, 0);
+ break;
+ case MC_VIEW_GOTO_OFFSET_DEC:
+ case MC_VIEW_GOTO_OFFSET_HEX:
+ if (!view->mode_flags.hex)
+ {
+ if (view->growbuf_in_use)
+ mcview_growbuf_read_until (view, addr);
+
+ *offset = mcview_bol (view, addr, 0);
+ }
+ else
+ {
+ /* read all data from pipe to get real size */
+ if (view->growbuf_in_use)
+ mcview_growbuf_read_all_data (view);
+
+ *offset = addr;
+ addr = mcview_get_filesize (view);
+ if (*offset > addr)
+ *offset = addr;
+ }
+ break;
+ default:
+ *offset = 0;
+ break;
+ }
+ }
+ }
+
+ g_free (exp);
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/display.c b/src/viewer/display.c
new file mode 100644
index 0000000..e76c4dd
--- /dev/null
+++ b/src/viewer/display.c
@@ -0,0 +1,404 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for whow info on display
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009, 2010
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/skin.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/setup.h" /* panels_options */
+#include "src/keymap.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define BUF_TRUNC_LEN 5 /* The length of the line displays the file size */
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* If set, show a ruler */
+static enum ruler_type
+{
+ RULER_NONE,
+ RULER_TOP,
+ RULER_BOTTOM
+} ruler = RULER_NONE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Define labels and handlers for functional keys */
+
+static void
+mcview_set_buttonbar (WView * view)
+{
+ Widget *w = WIDGET (view);
+ WDialog *h = DIALOG (w->owner);
+ WButtonBar *b;
+ const global_keymap_t *keymap = view->mode_flags.hex ? view->hex_keymap : w->keymap;
+
+ b = buttonbar_find (h);
+ buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), keymap, w);
+
+ if (view->mode_flags.hex)
+ {
+ if (view->hexedit_mode)
+ buttonbar_set_label (b, 2, Q_ ("ButtonBar|View"), keymap, w);
+ else if (view->datasource == DS_FILE)
+ buttonbar_set_label (b, 2, Q_ ("ButtonBar|Edit"), keymap, w);
+ else
+ buttonbar_set_label (b, 2, "", keymap, WIDGET (view));
+
+ buttonbar_set_label (b, 4, Q_ ("ButtonBar|Ascii"), keymap, w);
+ buttonbar_set_label (b, 6, Q_ ("ButtonBar|Save"), keymap, w);
+ buttonbar_set_label (b, 7, Q_ ("ButtonBar|HxSrch"), keymap, w);
+
+ }
+ else
+ {
+ buttonbar_set_label (b, 2, view->mode_flags.wrap ? Q_ ("ButtonBar|UnWrap")
+ : Q_ ("ButtonBar|Wrap"), keymap, w);
+ buttonbar_set_label (b, 4, Q_ ("ButtonBar|Hex"), keymap, w);
+ buttonbar_set_label (b, 6, "", keymap, WIDGET (view));
+ buttonbar_set_label (b, 7, Q_ ("ButtonBar|Search"), keymap, w);
+ }
+
+ buttonbar_set_label (b, 5, Q_ ("ButtonBar|Goto"), keymap, w);
+ buttonbar_set_label (b, 8, view->mode_flags.magic ? Q_ ("ButtonBar|Raw")
+ : Q_ ("ButtonBar|Parse"), keymap, w);
+
+ if (!mcview_is_in_panel (view)) /* don't override some panel buttonbar keys */
+ {
+ buttonbar_set_label (b, 3, Q_ ("ButtonBar|Quit"), keymap, w);
+ buttonbar_set_label (b, 9, view->mode_flags.nroff ? Q_ ("ButtonBar|Unform")
+ : Q_ ("ButtonBar|Format"), keymap, w);
+ buttonbar_set_label (b, 10, Q_ ("ButtonBar|Quit"), keymap, w);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_display_percent (WView * view, off_t p)
+{
+ int percent;
+
+ percent = mcview_calc_percent (view, p);
+ if (percent >= 0)
+ {
+ int top = view->status_area.y;
+ int right;
+
+ right = view->status_area.x + view->status_area.cols;
+ widget_gotoyx (view, top, right - 4);
+ tty_printf ("%3d%%", percent);
+ /* avoid cursor wrapping in NCurses-base MC */
+ widget_gotoyx (view, top, right - 1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_display_status (WView * view)
+{
+ const WRect *r = &view->status_area;
+ const char *file_label;
+
+ if (r->lines < 1)
+ return;
+
+ tty_setcolor (STATUSBAR_COLOR);
+ tty_draw_hline (WIDGET (view)->rect.y + r->y, WIDGET (view)->rect.x + r->x, ' ', r->cols);
+
+ file_label =
+ view->filename_vpath != NULL ?
+ vfs_path_get_last_path_str (view->filename_vpath) : view->command != NULL ?
+ view->command : "";
+
+ if (r->cols > 40)
+ {
+ widget_gotoyx (view, r->y, r->cols - 32);
+ if (view->mode_flags.hex)
+ tty_printf ("0x%08" PRIxMAX, (uintmax_t) view->hex_cursor);
+ else
+ {
+ char buffer[BUF_TRUNC_LEN + 1];
+
+ size_trunc_len (buffer, BUF_TRUNC_LEN, mcview_get_filesize (view), 0,
+ panels_options.kilobyte_si);
+ tty_printf ("%9" PRIuMAX "/%s%s %s", (uintmax_t) view->dpy_end,
+ buffer, mcview_may_still_grow (view) ? "+" : " ",
+#ifdef HAVE_CHARSET
+ mc_global.source_codepage >= 0 ?
+ get_codepage_id (mc_global.source_codepage) :
+#endif
+ "");
+ }
+ }
+ widget_gotoyx (view, r->y, r->x);
+ if (r->cols > 40)
+ tty_print_string (str_fit_to_term (file_label, r->cols - 34, J_LEFT_FIT));
+ else
+ tty_print_string (str_fit_to_term (file_label, r->cols - 5, J_LEFT_FIT));
+ if (r->cols > 26)
+ mcview_display_percent (view, view->mode_flags.hex ? view->hex_cursor : view->dpy_end);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_update (WView * view)
+{
+ static int dirt_limit = 1;
+
+ if (view->dpy_bbar_dirty)
+ {
+ view->dpy_bbar_dirty = FALSE;
+ mcview_set_buttonbar (view);
+ widget_draw (WIDGET (buttonbar_find (DIALOG (WIDGET (view)->owner))));
+ }
+
+ if (view->dirty > dirt_limit)
+ {
+ /* Too many updates skipped -> force a update */
+ mcview_display (view);
+ view->dirty = 0;
+ /* Raise the update skipping limit */
+ dirt_limit++;
+ if (dirt_limit > mcview_max_dirt_limit)
+ dirt_limit = mcview_max_dirt_limit;
+ }
+ else if (view->dirty > 0)
+ {
+ if (is_idle ())
+ {
+ /* We have time to update the screen properly */
+ mcview_display (view);
+ view->dirty = 0;
+ if (dirt_limit > 1)
+ dirt_limit--;
+ }
+ else
+ {
+ /* We are busy -> skipping full update,
+ only the status line is updated */
+ mcview_display_status (view);
+ }
+ /* Here we had a refresh, if fast scrolling does not work
+ restore the refresh, although this should not happen */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Displays as much data from view->dpy_start as fits on the screen */
+
+void
+mcview_display (WView * view)
+{
+ if (view->mode_flags.hex)
+ mcview_display_hex (view);
+ else
+ mcview_display_text (view);
+ mcview_display_status (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_compute_areas (WView * view)
+{
+ WRect view_area;
+ int height, rest, y;
+
+ /* The viewer is surrounded by a frame of size view->dpy_frame_size.
+ * Inside that frame, there are: The status line (at the top),
+ * the data area and an optional ruler, which is shown above or
+ * below the data area. */
+
+ view_area.y = view->dpy_frame_size;
+ view_area.x = view->dpy_frame_size;
+ view_area.lines = DOZ (WIDGET (view)->rect.lines, 2 * view->dpy_frame_size);
+ view_area.cols = DOZ (WIDGET (view)->rect.cols, 2 * view->dpy_frame_size);
+
+ /* Most coordinates of the areas equal those of the whole viewer */
+ view->status_area = view_area;
+ view->ruler_area = view_area;
+ view->data_area = view_area;
+
+ /* Compute the heights of the areas */
+ rest = view_area.lines;
+
+ height = MIN (rest, 1);
+ view->status_area.lines = height;
+ rest -= height;
+
+ height = (ruler == RULER_NONE || view->mode_flags.hex) ? 0 : 2;
+ height = MIN (rest, height);
+ view->ruler_area.lines = height;
+ rest -= height;
+
+ view->data_area.lines = rest;
+
+ /* Compute the position of the areas */
+ y = view_area.y;
+
+ view->status_area.y = y;
+ y += view->status_area.lines;
+
+ if (ruler == RULER_TOP)
+ {
+ view->ruler_area.y = y;
+ y += view->ruler_area.lines;
+ }
+
+ view->data_area.y = y;
+ y += view->data_area.lines;
+
+ if (ruler == RULER_BOTTOM)
+ view->ruler_area.y = y;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_update_bytes_per_line (WView * view)
+{
+ int cols = view->data_area.cols;
+ int bytes;
+
+ if (cols < 9 + 17)
+ bytes = 4;
+ else
+ bytes = 4 * ((cols - 9) / ((cols <= 80) ? 17 : 18));
+
+ g_assert (bytes != 0);
+
+ view->bytes_per_line = bytes;
+ view->dirty = mcview_max_dirt_limit + 1; /* To force refresh */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_display_toggle_ruler (WView * view)
+{
+ static const enum ruler_type next[3] =
+ {
+ RULER_TOP,
+ RULER_BOTTOM,
+ RULER_NONE
+ };
+
+ g_assert ((size_t) ruler < 3);
+
+ ruler = next[(size_t) ruler];
+ mcview_compute_areas (view);
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_display_clean (WView * view)
+{
+ Widget *w = WIDGET (view);
+
+ tty_setcolor (VIEW_NORMAL_COLOR);
+ widget_erase (w);
+ if (view->dpy_frame_size != 0)
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_display_ruler (WView * view)
+{
+ static const char ruler_chars[] = "|----*----";
+ const WRect *r = &view->ruler_area;
+ const int line_row = (ruler == RULER_TOP) ? 0 : 1;
+ const int nums_row = (ruler == RULER_TOP) ? 1 : 0;
+
+ char r_buff[10];
+ off_t cl;
+ int c;
+
+ if (ruler == RULER_NONE || r->lines < 1)
+ return;
+
+ tty_setcolor (VIEW_BOLD_COLOR);
+ for (c = 0; c < r->cols; c++)
+ {
+ cl = view->dpy_text_column + c;
+ if (line_row < r->lines)
+ {
+ widget_gotoyx (view, r->y + line_row, r->x + c);
+ tty_print_char (ruler_chars[cl % 10]);
+ }
+
+ if ((cl != 0) && (cl % 10) == 0)
+ {
+ g_snprintf (r_buff, sizeof (r_buff), "%" PRIuMAX, (uintmax_t) cl);
+ if (nums_row < r->lines)
+ {
+ widget_gotoyx (view, r->y + nums_row, r->x + c - 1);
+ tty_print_string (r_buff);
+ }
+ }
+ }
+ tty_setcolor (VIEW_NORMAL_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/growbuf.c b/src/viewer/growbuf.c
new file mode 100644
index 0000000..e18a527
--- /dev/null
+++ b/src/viewer/growbuf.c
@@ -0,0 +1,297 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for work with growing buffers
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009, 2014
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* D_NORMAL */
+
+#include "internal.h"
+
+/* Block size for reading files in parts */
+#define VIEW_PAGE_SIZE ((size_t) 8192)
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_growbuf_init (WView * view)
+{
+ view->growbuf_in_use = TRUE;
+ view->growbuf_blockptr = g_ptr_array_new_with_free_func (g_free);
+ view->growbuf_lastindex = VIEW_PAGE_SIZE;
+ view->growbuf_finished = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_growbuf_done (WView * view)
+{
+ view->growbuf_finished = TRUE;
+
+ if (view->datasource == DS_STDIO_PIPE)
+ {
+ mc_pclose (view->ds_stdio_pipe, NULL);
+ view->ds_stdio_pipe = NULL;
+ }
+ else /* view->datasource == DS_VFS_PIPE */
+ {
+ (void) mc_close (view->ds_vfs_pipe);
+ view->ds_vfs_pipe = -1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_growbuf_free (WView * view)
+{
+ g_assert (view->growbuf_in_use);
+
+ g_ptr_array_free (view->growbuf_blockptr, TRUE);
+ view->growbuf_blockptr = NULL;
+ view->growbuf_in_use = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+mcview_growbuf_filesize (WView * view)
+{
+ g_assert (view->growbuf_in_use);
+
+ if (view->growbuf_blockptr->len == 0)
+ return 0;
+ else
+ return ((off_t) view->growbuf_blockptr->len - 1) * VIEW_PAGE_SIZE + view->growbuf_lastindex;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Copies the output from the pipe to the growing buffer, until either
+ * the end-of-pipe is reached or the interval [0..ofs) of the growing
+ * buffer is completely filled.
+ */
+
+void
+mcview_growbuf_read_until (WView * view, off_t ofs)
+{
+ gboolean short_read = FALSE;
+
+ g_assert (view->growbuf_in_use);
+
+ if (view->growbuf_finished)
+ return;
+
+ while (mcview_growbuf_filesize (view) < ofs || short_read)
+ {
+ ssize_t nread = 0;
+ byte *p;
+ size_t bytesfree;
+
+ if (view->growbuf_lastindex == VIEW_PAGE_SIZE)
+ {
+ /* Append a new block to the growing buffer */
+ byte *newblock = g_try_malloc (VIEW_PAGE_SIZE);
+ if (newblock == NULL)
+ return;
+
+ g_ptr_array_add (view->growbuf_blockptr, newblock);
+ view->growbuf_lastindex = 0;
+ }
+
+ p = (byte *) g_ptr_array_index (view->growbuf_blockptr,
+ view->growbuf_blockptr->len - 1) + view->growbuf_lastindex;
+
+ bytesfree = VIEW_PAGE_SIZE - view->growbuf_lastindex;
+
+ if (view->datasource == DS_STDIO_PIPE)
+ {
+ mc_pipe_t *sp = view->ds_stdio_pipe;
+ GError *error = NULL;
+
+ if (bytesfree > MC_PIPE_BUFSIZE)
+ bytesfree = MC_PIPE_BUFSIZE;
+
+ sp->out.len = bytesfree;
+ sp->err.len = MC_PIPE_BUFSIZE;
+
+ mc_pread (sp, &error);
+
+ if (error != NULL)
+ {
+ mcview_show_error (view, error->message);
+ g_error_free (error);
+ mcview_growbuf_done (view);
+ return;
+ }
+
+ if (view->pipe_first_err_msg && sp->err.len > 0)
+ {
+ /* ignore possible following errors */
+ /* reset this flag before call of mcview_show_error() to break
+ * endless recursion: mcview_growbuf_read_until() -> mcview_show_error() ->
+ * MSG_DRAW -> mcview_display() -> mcview_get_byte() -> mcview_growbuf_read_until()
+ */
+ view->pipe_first_err_msg = FALSE;
+
+ mcview_show_error (view, sp->err.buf);
+
+ /* when switch from parse to raw mode and back,
+ * do not close the already closed pipe (see call to mcview_growbuf_done below).
+ * return from here since (sp == view->ds_stdio_pipe) would now be invalid.
+ * NOTE: this check was removed by ticket #4103 but the above call to
+ * mcview_show_error triggers the stdio pipe handle to be closed:
+ * mcview_close_datasource -> mcview_growbuf_done
+ */
+ if (view->ds_stdio_pipe == NULL)
+ return;
+ }
+
+ if (sp->out.len > 0)
+ {
+ memmove (p, sp->out.buf, sp->out.len);
+ nread = sp->out.len;
+ }
+ else if (sp->out.len == MC_PIPE_STREAM_EOF || sp->out.len == MC_PIPE_ERROR_READ)
+ {
+ if (sp->out.len == MC_PIPE_ERROR_READ)
+ {
+ char *err_msg;
+
+ err_msg = g_strdup_printf (_("Failed to read data from child stdout:\n%s"),
+ unix_error_string (sp->out.error));
+ mcview_show_error (view, err_msg);
+ g_free (err_msg);
+ }
+
+ /* when switch from parse to raw mode and back,
+ * do not close the already closed pipe after following loop:
+ * mcview_growbuf_read_until() -> mcview_show_error() ->
+ * MSG_DRAW -> mcview_display() -> mcview_get_byte() -> mcview_growbuf_read_until()
+ */
+ mcview_growbuf_done (view);
+
+ mcview_display (view);
+ return;
+ }
+ }
+ else
+ {
+ g_assert (view->datasource == DS_VFS_PIPE);
+ do
+ {
+ nread = mc_read (view->ds_vfs_pipe, p, bytesfree);
+ }
+ while (nread == -1 && errno == EINTR);
+
+ if (nread <= 0)
+ {
+ mcview_growbuf_done (view);
+ return;
+ }
+ }
+ short_read = ((size_t) nread < bytesfree);
+ view->growbuf_lastindex += nread;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_get_byte_growing_buffer (WView * view, off_t byte_index, int *retval)
+{
+ char *p;
+
+ g_assert (view->growbuf_in_use);
+
+ if (retval != NULL)
+ *retval = -1;
+
+ if (byte_index < 0)
+ return FALSE;
+
+ p = mcview_get_ptr_growing_buffer (view, byte_index);
+ if (p == NULL)
+ return FALSE;
+
+ if (retval != NULL)
+ *retval = (unsigned char) (*p);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mcview_get_ptr_growing_buffer (WView * view, off_t byte_index)
+{
+ off_t pageno, pageindex;
+
+ g_assert (view->growbuf_in_use);
+
+ if (byte_index < 0)
+ return NULL;
+
+ pageno = byte_index / VIEW_PAGE_SIZE;
+ pageindex = byte_index % VIEW_PAGE_SIZE;
+
+ mcview_growbuf_read_until (view, byte_index + 1);
+ if (view->growbuf_blockptr->len == 0)
+ return NULL;
+ if (pageno < (off_t) view->growbuf_blockptr->len - 1)
+ return ((char *) g_ptr_array_index (view->growbuf_blockptr, pageno) + pageindex);
+ if (pageno == (off_t) view->growbuf_blockptr->len - 1
+ && pageindex < (off_t) view->growbuf_lastindex)
+ return ((char *) g_ptr_array_index (view->growbuf_blockptr, pageno) + pageindex);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/hex.c b/src/viewer/hex.c
new file mode 100644
index 0000000..c0cf7d0
--- /dev/null
+++ b/src/viewer/hex.c
@@ -0,0 +1,484 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for hex view
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/lock.h" /* lock_file() and unlock_file() */
+#include "lib/util.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ MARK_NORMAL,
+ MARK_SELECTED,
+ MARK_CURSOR,
+ MARK_CHANGED
+} mark_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const char hex_char[] = "0123456789ABCDEF";
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/** Determine the state of the current byte.
+ *
+ * @param view viewer object
+ * @param from offset
+ * @param curr current node
+ */
+
+static mark_t
+mcview_hex_calculate_boldflag (WView * view, off_t from, struct hexedit_change_node *curr,
+ gboolean force_changed)
+{
+ return (from == view->hex_cursor) ? MARK_CURSOR
+ : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
+ : (view->search_start <= from && from < view->search_end) ? MARK_SELECTED : MARK_NORMAL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_display_hex (WView * view)
+{
+ const WRect *r = &view->data_area;
+ int ngroups = view->bytes_per_line / 4;
+ /* 8 characters are used for the file offset, and every hex group
+ * takes 13 characters. Starting at width of 80 columns, the groups
+ * are separated by an extra vertical line. Starting at width of 81,
+ * there is an extra space before the text column. There is always a
+ * mostly empty column on the right, to allow overflowing CJKs.
+ */
+ int text_start;
+
+ int row = 0;
+ off_t from;
+ mark_t boldflag_byte = MARK_NORMAL;
+ mark_t boldflag_char = MARK_NORMAL;
+ struct hexedit_change_node *curr = view->change_list;
+#ifdef HAVE_CHARSET
+ int cont_bytes = 0; /* number of continuation bytes remanining from current UTF-8 */
+ gboolean cjk_right = FALSE; /* whether the second byte of a CJK is to be processed */
+#endif /* HAVE_CHARSET */
+ gboolean utf8_changed = FALSE; /* whether any of the bytes in the UTF-8 were changed */
+
+ char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
+
+ text_start = 8 + 13 * ngroups +
+ ((r->cols < 80) ? 0 : (r->cols == 80) ? (ngroups - 1) : (ngroups - 1 + 1));
+
+ mcview_display_clean (view);
+
+ /* Find the first displayable changed byte */
+ /* In UTF-8 mode, go back by 1 or maybe 2 lines to handle continuation bytes properly. */
+ from = view->dpy_start;
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ {
+ if (from >= view->bytes_per_line)
+ {
+ row--;
+ from -= view->bytes_per_line;
+ }
+ if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
+ {
+ row--;
+ from -= view->bytes_per_line;
+ }
+ }
+#endif /* HAVE_CHARSET */
+ while (curr && (curr->offset < from))
+ {
+ curr = curr->next;
+ }
+
+ for (; mcview_get_byte (view, from, NULL) && row < r->lines; row++)
+ {
+ int col = 0;
+ int bytes; /* Number of bytes already printed on the line */
+
+ /* Print the hex offset */
+ if (row >= 0)
+ {
+ int i;
+
+ g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
+ widget_gotoyx (view, r->y + row, r->x);
+ tty_setcolor (VIEW_BOLD_COLOR);
+ for (i = 0; col < r->cols && hex_buff[i] != '\0'; col++, i++)
+ tty_print_char (hex_buff[i]);
+ tty_setcolor (VIEW_NORMAL_COLOR);
+ }
+
+ for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
+ {
+ int c;
+#ifdef HAVE_CHARSET
+ int ch = 0;
+
+ if (view->utf8)
+ {
+ struct hexedit_change_node *corr = curr;
+
+ if (cont_bytes != 0)
+ {
+ /* UTF-8 continuation bytes, print a space (with proper attributes)... */
+ cont_bytes--;
+ ch = ' ';
+ if (cjk_right)
+ {
+ /* ... except when it'd wipe out the right half of a CJK, then print nothing */
+ cjk_right = FALSE;
+ ch = -1;
+ }
+ }
+ else
+ {
+ int j;
+ gchar utf8buf[UTF8_CHAR_LEN + 1];
+ int res;
+ int first_changed = -1;
+
+ for (j = 0; j < UTF8_CHAR_LEN; j++)
+ {
+ if (mcview_get_byte (view, from + j, &res))
+ utf8buf[j] = res;
+ else
+ {
+ utf8buf[j] = '\0';
+ break;
+ }
+ if (curr != NULL && from + j == curr->offset)
+ {
+ utf8buf[j] = curr->value;
+ if (first_changed == -1)
+ first_changed = j;
+ }
+ if (curr != NULL && from + j >= curr->offset)
+ curr = curr->next;
+ }
+ utf8buf[UTF8_CHAR_LEN] = '\0';
+
+ /* Determine the state of the current multibyte char */
+ ch = g_utf8_get_char_validated (utf8buf, -1);
+ if (ch == -1 || ch == -2)
+ {
+ ch = '.';
+ }
+ else
+ {
+ gchar *next_ch;
+
+ next_ch = g_utf8_next_char (utf8buf);
+ cont_bytes = next_ch - utf8buf - 1;
+ if (g_unichar_iswide (ch))
+ cjk_right = TRUE;
+ }
+
+ utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
+ curr = corr;
+ }
+ }
+#endif /* HAVE_CHARSET */
+
+ /* For negative rows, the only thing we care about is overflowing
+ * UTF-8 continuation bytes which were handled above. */
+ if (row < 0)
+ {
+ if (curr != NULL && from == curr->offset)
+ curr = curr->next;
+ continue;
+ }
+
+ if (!mcview_get_byte (view, from, &c))
+ break;
+
+ /* Save the cursor position for mcview_place_cursor() */
+ if (from == view->hex_cursor && !view->hexview_in_text)
+ {
+ view->cursor_row = row;
+ view->cursor_col = col;
+ }
+
+ /* Determine the state of the current byte */
+ boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
+ boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
+
+ /* Determine the value of the current byte */
+ if (curr != NULL && from == curr->offset)
+ {
+ c = curr->value;
+ curr = curr->next;
+ }
+
+ /* Select the color for the hex number */
+ tty_setcolor (boldflag_byte == MARK_NORMAL ? VIEW_NORMAL_COLOR :
+ boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR :
+ boldflag_byte == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
+ /* boldflag_byte == MARK_CURSOR */
+ view->hexview_in_text ? VIEW_SELECTED_COLOR : VIEW_UNDERLINED_COLOR);
+
+ /* Print the hex number */
+ widget_gotoyx (view, r->y + row, r->x + col);
+ if (col < r->cols)
+ {
+ tty_print_char (hex_char[c / 16]);
+ col += 1;
+ }
+ if (col < r->cols)
+ {
+ tty_print_char (hex_char[c % 16]);
+ col += 1;
+ }
+
+ /* Print the separator */
+ tty_setcolor (VIEW_NORMAL_COLOR);
+ if (bytes != view->bytes_per_line - 1)
+ {
+ if (col < r->cols)
+ {
+ tty_print_char (' ');
+ col += 1;
+ }
+
+ /* After every four bytes, print a group separator */
+ if (bytes % 4 == 3)
+ {
+ if (view->data_area.cols >= 80 && col < r->cols)
+ {
+ tty_print_one_vline (TRUE);
+ col += 1;
+ }
+ if (col < r->cols)
+ {
+ tty_print_char (' ');
+ col += 1;
+ }
+ }
+ }
+
+ /* Select the color for the character; this differs from the
+ * hex color when boldflag == MARK_CURSOR */
+ tty_setcolor (boldflag_char == MARK_NORMAL ? VIEW_NORMAL_COLOR :
+ boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR :
+ boldflag_char == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
+ /* boldflag_char == MARK_CURSOR */
+ view->hexview_in_text ? VIEW_SELECTED_COLOR : MARKED_SELECTED_COLOR);
+
+
+#ifdef HAVE_CHARSET
+ if (mc_global.utf8_display)
+ {
+ if (!view->utf8)
+ {
+ c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
+ }
+ if (!g_unichar_isprint (c))
+ c = '.';
+ }
+ else if (view->utf8)
+ ch = convert_from_utf_to_current_c (ch, view->converter);
+ else
+#endif
+ {
+#ifdef HAVE_CHARSET
+ c = convert_to_display_c (c);
+#endif
+
+ if (!is_printable (c))
+ c = '.';
+ }
+
+ /* Print corresponding character on the text side */
+ if (text_start + bytes < r->cols)
+ {
+ widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ tty_print_anychar (ch);
+ else
+#endif
+ tty_print_char (c);
+ }
+
+ /* Save the cursor position for mcview_place_cursor() */
+ if (from == view->hex_cursor && view->hexview_in_text)
+ {
+ view->cursor_row = row;
+ view->cursor_col = text_start + bytes;
+ }
+ }
+ }
+
+ /* Be polite to the other functions */
+ tty_setcolor (VIEW_NORMAL_COLOR);
+
+ mcview_place_cursor (view);
+ view->dpy_end = from;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_hexedit_save_changes (WView * view)
+{
+ int answer = 0;
+
+ if (view->change_list == NULL)
+ return TRUE;
+
+ while (answer == 0)
+ {
+ int fp;
+ char *text;
+ struct hexedit_change_node *curr, *next;
+
+ g_assert (view->filename_vpath != NULL);
+
+ fp = mc_open (view->filename_vpath, O_WRONLY);
+ if (fp != -1)
+ {
+ for (curr = view->change_list; curr != NULL; curr = next)
+ {
+ next = curr->next;
+
+ if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
+ || mc_write (fp, &(curr->value), 1) != 1)
+ goto save_error;
+
+ /* delete the saved item from the change list */
+ view->change_list = next;
+ view->dirty++;
+ mcview_set_byte (view, curr->offset, curr->value);
+ g_free (curr);
+ }
+
+ view->change_list = NULL;
+
+ if (view->locked)
+ view->locked = unlock_file (view->filename_vpath);
+
+ if (mc_close (fp) == -1)
+ message (D_ERROR, _("Save file"),
+ _("Error while closing the file:\n%s\n"
+ "Data may have been written or not"), unix_error_string (errno));
+
+ view->dirty++;
+ return TRUE;
+ }
+
+ save_error:
+ text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
+ (void) mc_close (fp);
+
+ answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
+ g_free (text);
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_hexedit_mode (WView * view)
+{
+ view->hexedit_mode = !view->hexedit_mode;
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_hexedit_free_change_list (WView * view)
+{
+ struct hexedit_change_node *curr, *next;
+
+ for (curr = view->change_list; curr != NULL; curr = next)
+ {
+ next = curr->next;
+ g_free (curr);
+ }
+ view->change_list = NULL;
+
+ if (view->locked)
+ view->locked = unlock_file (view->filename_vpath);
+
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
+{
+ /* chnode always either points to the head of the list or
+ * to one of the ->next fields in the list. The value at
+ * this location will be overwritten with the new node. */
+ struct hexedit_change_node **chnode = head;
+
+ while (*chnode != NULL && (*chnode)->offset < node->offset)
+ chnode = &((*chnode)->next);
+
+ node->next = *chnode;
+ *chnode = node;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/internal.h b/src/viewer/internal.h
new file mode 100644
index 0000000..566b07c
--- /dev/null
+++ b/src/viewer/internal.h
@@ -0,0 +1,472 @@
+#ifndef MC__VIEWER_INTERNAL_H
+#define MC__VIEWER_INTERNAL_H
+
+#include <limits.h> /* CHAR_BIT */
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/search.h"
+#include "lib/widget.h"
+#include "lib/vfs/vfs.h" /* vfs_path_t */
+#include "lib/util.h" /* mc_pipe_t */
+
+#include "src/keymap.h" /* global_keymap_t */
+#include "src/filemanager/dir.h" /* dir_list */
+
+#include "mcviewer.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define OFF_T_BITWIDTH ((unsigned int) (sizeof (off_t) * CHAR_BIT - 1))
+#define OFFSETTYPE_MAX (((off_t) 1 << (OFF_T_BITWIDTH - 1)) - 1)
+
+typedef unsigned char byte;
+
+/*** enums ***************************************************************************************/
+
+/* data sources of the view */
+enum view_ds
+{
+ DS_NONE, /* No data available */
+ DS_STDIO_PIPE, /* Data comes from a pipe using popen/pclose */
+ DS_VFS_PIPE, /* Data comes from a piped-in VFS file */
+ DS_FILE, /* Data comes from a VFS file */
+ DS_STRING /* Data comes from a string in memory */
+};
+
+enum ccache_type
+{
+ CCACHE_OFFSET,
+ CCACHE_LINECOL
+};
+
+typedef enum
+{
+ NROFF_TYPE_NONE = 0,
+ NROFF_TYPE_BOLD = 1,
+ NROFF_TYPE_UNDERLINE = 2
+} nroff_type_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* A node for building a change list on change_list */
+struct hexedit_change_node
+{
+ struct hexedit_change_node *next;
+ off_t offset;
+ byte value;
+};
+
+/* A cache entry for mapping offsets into line/column pairs and vice versa.
+ * cc_offset, cc_line, and cc_column are the 0-based values of the offset,
+ * line and column of that cache entry. cc_nroff_column is the column
+ * corresponding to cc_offset in nroff mode.
+ */
+typedef struct
+{
+ off_t cc_offset;
+ off_t cc_line;
+ off_t cc_column;
+ off_t cc_nroff_column;
+} coord_cache_entry_t;
+
+/* TODO: find a better name. This is not actually a "state machine",
+ * but a "state machine's state", but that sounds silly.
+ * Could be parser_state, formatter_state... */
+typedef struct
+{
+ off_t offset; /* The file offset at which this is the state. */
+ off_t unwrapped_column; /* Columns if the paragraph wasn't wrapped, */
+ /* used for positioning TABs in wrapped lines */
+ gboolean nroff_underscore_is_underlined; /* whether _\b_ is underlined rather than bold */
+ gboolean print_lonely_combining; /* whether lonely combining marks are printed on a dotted circle */
+} mcview_state_machine_t;
+
+struct mcview_nroff_struct;
+
+struct WView
+{
+ Widget widget;
+
+ vfs_path_t *filename_vpath; /* Name of the file */
+ vfs_path_t *workdir_vpath; /* Name of the working directory */
+ char *command; /* Command used to pipe data in */
+
+ enum view_ds datasource; /* Where the displayed data comes from */
+
+ /* stdio pipe data source */
+ mc_pipe_t *ds_stdio_pipe; /* Output of a shell command */
+ gboolean pipe_first_err_msg; /* Show only 1st message from stderr */
+
+ /* vfs pipe data source */
+ int ds_vfs_pipe; /* Non-seekable vfs file descriptor */
+
+ /* vfs file data source */
+ int ds_file_fd; /* File with random access */
+ off_t ds_file_filesize; /* Size of the file */
+ off_t ds_file_offset; /* Offset of the currently loaded data */
+ byte *ds_file_data; /* Currently loaded data */
+ size_t ds_file_datalen; /* Number of valid bytes in file_data */
+ size_t ds_file_datasize; /* Number of allocated bytes in file_data */
+
+ /* string data source */
+ byte *ds_string_data; /* The characters of the string */
+ size_t ds_string_len; /* The length of the string */
+
+ /* Growing buffers information */
+ gboolean growbuf_in_use; /* Use the growing buffers? */
+ GPtrArray *growbuf_blockptr; /* Pointer to the block pointers */
+ size_t growbuf_lastindex; /* Number of bytes in the last page of the
+ growing buffer */
+ gboolean growbuf_finished; /* TRUE when all data has been read. */
+
+ mcview_mode_flags_t mode_flags;
+
+ /* Hex editor modes */
+ gboolean hexedit_mode; /* Hexview or Hexedit */
+ const global_keymap_t *hex_keymap;
+ gboolean hexview_in_text; /* Is the hexview cursor in the text area? */
+ int bytes_per_line; /* Number of bytes per line in hex mode */
+ off_t hex_cursor; /* Hexview cursor position in file */
+ gboolean hexedit_lownibble; /* Are we editing the last significant nibble? */
+ gboolean locked; /* We hold lock on current file */
+
+#ifdef HAVE_CHARSET
+ gboolean utf8; /* It's multibyte file codeset */
+#endif
+
+ GPtrArray *coord_cache; /* Cache for mapping offsets to cursor positions */
+
+ /* Display information */
+ int dpy_frame_size; /* Size of the frame surrounding the real viewer */
+ off_t dpy_start; /* Offset of the displayed data (start of the paragraph in non-hex mode) */
+ off_t dpy_end; /* Offset after the displayed data */
+ off_t dpy_paragraph_skip_lines; /* Extra lines to skip in wrap mode */
+ mcview_state_machine_t dpy_state_top; /* Parser-formatter state at the topmost visible line in wrap mode */
+ mcview_state_machine_t dpy_state_bottom; /* Parser-formatter state after the bottomvisible line in wrap mode */
+ gboolean dpy_wrap_dirty; /* dpy_state_top needs to be recomputed */
+ off_t dpy_text_column; /* Number of skipped columns in non-wrap
+ * text mode */
+ int cursor_col; /* Cursor column */
+ int cursor_row; /* Cursor row */
+ struct hexedit_change_node *change_list; /* Linked list of changes */
+ WRect status_area; /* Where the status line is displayed */
+ WRect ruler_area; /* Where the ruler is displayed */
+ WRect data_area; /* Where the data is displayed */
+
+ ssize_t force_max; /* Force a max offset, or -1 */
+
+ int dirty; /* Number of skipped updates */
+ gboolean dpy_bbar_dirty; /* Does the button bar need to be updated? */
+
+
+ /* handle of search engine */
+ mc_search_t *search;
+ gchar *last_search_string;
+ struct mcview_nroff_struct *search_nroff_seq;
+ off_t search_start; /* First character to start searching from */
+ off_t search_end; /* Length of found string or 0 if none was found */
+ int search_numNeedSkipChar;
+
+ /* Markers */
+ int marker; /* mark to use */
+ off_t marks[10]; /* 10 marks: 0..9 */
+
+ off_t update_steps; /* The number of bytes between percent increments */
+ off_t update_activate; /* Last point where we updated the status */
+
+ /* converter for translation of text */
+ GIConv converter;
+
+ GArray *saved_bookmarks;
+
+ dir_list *dir; /* List of current directory files
+ * to handle CK_FileNext and CK_FilePrev commands */
+ int *dir_idx; /* Index of current file in dir structure.
+ * Pointer is used here as reference to WPanel::dir::count */
+ vfs_path_t *ext_script; /* Temporary script file created by regex_command_for() */
+};
+
+typedef struct mcview_nroff_struct
+{
+ WView *view;
+ off_t index;
+ int char_length;
+ int current_char;
+ nroff_type_t type;
+ nroff_type_t prev_type;
+} mcview_nroff_t;
+
+typedef struct mcview_search_options_t
+{
+ mc_search_type_t type;
+ gboolean case_sens;
+ gboolean backwards;
+ gboolean whole_words;
+ gboolean all_codepages;
+} mcview_search_options_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern mcview_search_options_t mcview_search_options;
+
+/*** declarations of public functions ************************************************************/
+
+/* actions_cmd.c: */
+cb_ret_t mcview_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
+cb_ret_t mcview_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm,
+ void *data);
+
+/* ascii.c: */
+void mcview_display_text (WView * view);
+void mcview_state_machine_init (mcview_state_machine_t *, off_t);
+void mcview_ascii_move_down (WView * view, off_t lines);
+void mcview_ascii_move_up (WView * view, off_t lines);
+void mcview_ascii_moveto_bol (WView * view);
+void mcview_ascii_moveto_eol (WView * view);
+
+#ifdef MC_ENABLE_DEBUGGING_CODE
+void mcview_ccache_dump (WView * view);
+#endif
+
+void mcview_ccache_lookup (WView * view, coord_cache_entry_t * coord, enum ccache_type lookup_what);
+
+/* datasource.c: */
+void mcview_set_datasource_none (WView * view);
+off_t mcview_get_filesize (WView * view);
+void mcview_update_filesize (WView * view);
+char *mcview_get_ptr_file (WView * view, off_t byte_index);
+char *mcview_get_ptr_string (WView * view, off_t byte_index);
+gboolean mcview_get_utf (WView * view, off_t byte_index, int *ch, int *ch_len);
+gboolean mcview_get_byte_string (WView * view, off_t byte_index, int *retval);
+gboolean mcview_get_byte_none (WView * view, off_t byte_index, int *retval);
+void mcview_set_byte (WView * view, off_t offset, byte b);
+void mcview_file_load_data (WView * view, off_t byte_index);
+void mcview_close_datasource (WView * view);
+void mcview_set_datasource_file (WView * view, int fd, const struct stat *st);
+gboolean mcview_load_command_output (WView * view, const char *command);
+void mcview_set_datasource_vfs_pipe (WView * view, int fd);
+void mcview_set_datasource_string (WView * view, const char *s);
+
+/* dialog.c: */
+gboolean mcview_dialog_search (WView * view);
+gboolean mcview_dialog_goto (WView * view, off_t * offset);
+
+/* display.c: */
+void mcview_update (WView * view);
+void mcview_display (WView * view);
+void mcview_compute_areas (WView * view);
+void mcview_update_bytes_per_line (WView * view);
+void mcview_display_toggle_ruler (WView * view);
+void mcview_display_clean (WView * view);
+void mcview_display_ruler (WView * view);
+
+/* growbuf.c: */
+void mcview_growbuf_init (WView * view);
+void mcview_growbuf_done (WView * view);
+void mcview_growbuf_free (WView * view);
+off_t mcview_growbuf_filesize (WView * view);
+void mcview_growbuf_read_until (WView * view, off_t ofs);
+gboolean mcview_get_byte_growing_buffer (WView * view, off_t byte_index, int *retval);
+char *mcview_get_ptr_growing_buffer (WView * view, off_t byte_index);
+
+/* hex.c: */
+void mcview_display_hex (WView * view);
+gboolean mcview_hexedit_save_changes (WView * view);
+void mcview_toggle_hexedit_mode (WView * view);
+void mcview_hexedit_free_change_list (WView * view);
+void mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node);
+
+/* lib.c: */
+void mcview_toggle_magic_mode (WView * view);
+void mcview_toggle_wrap_mode (WView * view);
+void mcview_toggle_nroff_mode (WView * view);
+void mcview_toggle_hex_mode (WView * view);
+void mcview_init (WView * view);
+void mcview_done (WView * view);
+#ifdef HAVE_CHARSET
+void mcview_select_encoding (WView * view);
+void mcview_set_codeset (WView * view);
+#endif
+void mcview_show_error (WView * view, const char *error);
+off_t mcview_bol (WView * view, off_t current, off_t limit);
+off_t mcview_eol (WView * view, off_t current);
+char *mcview_get_title (const WDialog * h, size_t len);
+int mcview_calc_percent (WView * view, off_t p);
+
+/* move.c */
+void mcview_move_up (WView * view, off_t lines);
+void mcview_move_down (WView * view, off_t lines);
+void mcview_move_left (WView * view, off_t columns);
+void mcview_move_right (WView * view, off_t columns);
+void mcview_moveto_top (WView * view);
+void mcview_moveto_bottom (WView * view);
+void mcview_moveto_bol (WView * view);
+void mcview_moveto_eol (WView * view);
+void mcview_moveto_offset (WView * view, off_t offset);
+void mcview_moveto (WView * view, off_t, off_t col);
+void mcview_coord_to_offset (WView * view, off_t * ret_offset, off_t line, off_t column);
+void mcview_offset_to_coord (WView * view, off_t * ret_line, off_t * ret_column, off_t offset);
+void mcview_place_cursor (WView * view);
+void mcview_moveto_match (WView * view);
+
+/* nroff.c: */
+int mcview__get_nroff_real_len (WView * view, off_t start, off_t length);
+mcview_nroff_t *mcview_nroff_seq_new_num (WView * view, off_t lc_index);
+mcview_nroff_t *mcview_nroff_seq_new (WView * view);
+void mcview_nroff_seq_free (mcview_nroff_t ** nroff);
+nroff_type_t mcview_nroff_seq_info (mcview_nroff_t * nroff);
+int mcview_nroff_seq_next (mcview_nroff_t * nroff);
+int mcview_nroff_seq_prev (mcview_nroff_t * nroff);
+
+/* search.c: */
+gboolean mcview_search_init (WView * view);
+void mcview_search_deinit (WView * view);
+mc_search_cbret_t mcview_search_cmd_callback (const void *user_data, gsize char_offset,
+ int *current_char);
+mc_search_cbret_t mcview_search_update_cmd_callback (const void *user_data, gsize char_offset);
+void mcview_do_search (WView * view, off_t want_search_start);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline off_t
+mcview_offset_rounddown (off_t a, off_t b)
+{
+ g_assert (b != 0);
+ return a - a % b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* {{{ Simple Primitive Functions for WView }}} */
+static inline gboolean
+mcview_is_in_panel (WView * view)
+{
+ return (view->dpy_frame_size != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+mcview_may_still_grow (WView * view)
+{
+ return (view->growbuf_in_use && !view->growbuf_finished);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* returns TRUE if the idx lies in the half-open interval
+ * [offset; offset + size), FALSE otherwise.
+ */
+static inline gboolean
+mcview_already_loaded (off_t offset, off_t idx, size_t size)
+{
+ return (offset <= idx && idx - offset < (off_t) size);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+mcview_get_byte_file (WView * view, off_t byte_index, int *retval)
+{
+ g_assert (view->datasource == DS_FILE);
+
+ mcview_file_load_data (view, byte_index);
+ if (mcview_already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
+ {
+ if (retval)
+ *retval = view->ds_file_data[byte_index - view->ds_file_offset];
+ return TRUE;
+ }
+ if (retval)
+ *retval = -1;
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+mcview_get_byte (WView * view, off_t offset, int *retval)
+{
+ switch (view->datasource)
+ {
+ case DS_STDIO_PIPE:
+ case DS_VFS_PIPE:
+ return mcview_get_byte_growing_buffer (view, offset, retval);
+ case DS_FILE:
+ return mcview_get_byte_file (view, offset, retval);
+ case DS_STRING:
+ return mcview_get_byte_string (view, offset, retval);
+ case DS_NONE:
+ return mcview_get_byte_none (view, offset, retval);
+ default:
+ return FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+mcview_get_byte_indexed (WView * view, off_t base, off_t ofs, int *retval)
+{
+ if (base <= OFFSETTYPE_MAX - ofs)
+ return mcview_get_byte (view, base + ofs, retval);
+
+ if (retval != NULL)
+ *retval = -1;
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+mcview_count_backspaces (WView * view, off_t offset)
+{
+ int backspaces = 0;
+ int c;
+
+ while (offset >= 2 * backspaces && mcview_get_byte (view, offset - 2 * backspaces, &c)
+ && c == '\b')
+ backspaces++;
+
+ return backspaces;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+mcview_is_nroff_sequence (WView * view, off_t offset)
+{
+ int c0, c1, c2;
+
+ /* The following commands are ordered to speed up the calculation. */
+
+ if (!mcview_get_byte_indexed (view, offset, 1, &c1) || c1 != '\b')
+ return FALSE;
+
+ if (!mcview_get_byte_indexed (view, offset, 0, &c0) || !g_ascii_isprint (c0))
+ return FALSE;
+
+ if (!mcview_get_byte_indexed (view, offset, 2, &c2) || !g_ascii_isprint (c2))
+ return FALSE;
+
+ return (c0 == c2 || c0 == '_' || (c0 == '+' && c2 == 'o'));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mcview_growbuf_read_all_data (WView * view)
+{
+ mcview_growbuf_read_until (view, OFFSETTYPE_MAX);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__VIEWER_INTERNAL_H */
diff --git a/src/viewer/lib.c b/src/viewer/lib.c
new file mode 100644
index 0000000..5f2eb52
--- /dev/null
+++ b/src/viewer/lib.c
@@ -0,0 +1,437 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Common finctions (used from some other mcviewer functions)
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <string.h> /* memset() */
+#include <sys/types.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* save_file_position() */
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#ifdef HAVE_CHARSET
+#include "src/selcodepage.h"
+#endif
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_magic_mode (WView * view)
+{
+ char *filename, *command;
+ dir_list *dir;
+ int *dir_idx;
+
+ mcview_altered_flags.magic = TRUE;
+ view->mode_flags.magic = !view->mode_flags.magic;
+
+ /* reinit view */
+ filename = g_strdup (vfs_path_as_str (view->filename_vpath));
+ command = g_strdup (view->command);
+ dir = view->dir;
+ dir_idx = view->dir_idx;
+ view->dir = NULL;
+ view->dir_idx = NULL;
+ mcview_done (view);
+ mcview_init (view);
+ mcview_load (view, command, filename, 0, 0, 0);
+ view->dir = dir;
+ view->dir_idx = dir_idx;
+ g_free (filename);
+ g_free (command);
+
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_wrap_mode (WView * view)
+{
+ view->mode_flags.wrap = !view->mode_flags.wrap;
+ view->dpy_wrap_dirty = TRUE;
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_nroff_mode (WView * view)
+{
+ view->mode_flags.nroff = !view->mode_flags.nroff;
+ mcview_altered_flags.nroff = TRUE;
+ view->dpy_wrap_dirty = TRUE;
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_hex_mode (WView * view)
+{
+ view->mode_flags.hex = !view->mode_flags.hex;
+
+ if (view->mode_flags.hex)
+ {
+ view->hex_cursor = view->dpy_start;
+ view->dpy_start = mcview_offset_rounddown (view->dpy_start, view->bytes_per_line);
+ widget_want_cursor (WIDGET (view), TRUE);
+ }
+ else
+ {
+ view->dpy_start = mcview_bol (view, view->hex_cursor, 0);
+ view->hex_cursor = view->dpy_start;
+ widget_want_cursor (WIDGET (view), FALSE);
+ }
+ mcview_altered_flags.hex = TRUE;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_init (WView * view)
+{
+ size_t i;
+
+ view->filename_vpath = NULL;
+ view->workdir_vpath = NULL;
+ view->command = NULL;
+ view->search_nroff_seq = NULL;
+
+ mcview_set_datasource_none (view);
+
+ view->growbuf_in_use = FALSE;
+ /* leave the other growbuf fields uninitialized */
+
+ view->hexedit_lownibble = FALSE;
+ view->locked = FALSE;
+ view->coord_cache = NULL;
+
+ view->dpy_start = 0;
+ view->dpy_paragraph_skip_lines = 0;
+ mcview_state_machine_init (&view->dpy_state_top, 0);
+ view->dpy_wrap_dirty = FALSE;
+ view->force_max = -1;
+ view->dpy_text_column = 0;
+ view->dpy_end = 0;
+ view->hex_cursor = 0;
+ view->cursor_col = 0;
+ view->cursor_row = 0;
+ view->change_list = NULL;
+
+ /* {status,ruler,data}_area are left uninitialized */
+
+ view->dirty = 0;
+ view->dpy_bbar_dirty = TRUE;
+ view->bytes_per_line = 1;
+
+ view->search_start = 0;
+ view->search_end = 0;
+
+ view->marker = 0;
+ for (i = 0; i < G_N_ELEMENTS (view->marks); i++)
+ view->marks[i] = 0;
+
+ view->update_steps = 0;
+ view->update_activate = 0;
+
+ view->saved_bookmarks = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_done (WView * view)
+{
+ /* Save current file position */
+ if (mcview_remember_file_position && view->filename_vpath != NULL)
+ {
+ save_file_position (view->filename_vpath, -1, 0,
+ view->mode_flags.hex ? view->hex_cursor : view->dpy_start,
+ view->saved_bookmarks);
+ view->saved_bookmarks = NULL;
+ }
+
+ /* Write back the global viewer mode */
+ mcview_global_flags = view->mode_flags;
+
+ /* Free memory used by the viewer */
+ /* view->widget needs no destructor */
+ vfs_path_free (view->filename_vpath, TRUE);
+ view->filename_vpath = NULL;
+ vfs_path_free (view->workdir_vpath, TRUE);
+ view->workdir_vpath = NULL;
+ MC_PTR_FREE (view->command);
+
+ mcview_close_datasource (view);
+ /* the growing buffer is freed with the datasource */
+
+ if (view->coord_cache != NULL)
+ {
+ g_ptr_array_free (view->coord_cache, TRUE);
+ view->coord_cache = NULL;
+ }
+
+ if (view->converter == INVALID_CONV)
+ view->converter = str_cnv_from_term;
+
+ if (view->converter != str_cnv_from_term)
+ {
+ str_close_conv (view->converter);
+ view->converter = str_cnv_from_term;
+ }
+
+ mcview_search_deinit (view);
+ view->search = NULL;
+ view->last_search_string = NULL;
+ mcview_hexedit_free_change_list (view);
+
+ if (mc_global.mc_run_mode == MC_RUN_VIEWER && view->dir != NULL)
+ {
+ /* mcviewer is the owner of file list */
+ dir_list_free_list (view->dir);
+ g_free (view->dir);
+ g_free (view->dir_idx);
+ }
+
+ view->dir = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+void
+mcview_set_codeset (WView * view)
+{
+ const char *cp_id = NULL;
+
+ view->utf8 = TRUE;
+ cp_id =
+ get_codepage_id (mc_global.source_codepage >=
+ 0 ? mc_global.source_codepage : mc_global.display_codepage);
+ if (cp_id != NULL)
+ {
+ GIConv conv;
+ conv = str_crt_conv_from (cp_id);
+ if (conv != INVALID_CONV)
+ {
+ if (view->converter != str_cnv_from_term)
+ str_close_conv (view->converter);
+ view->converter = conv;
+ }
+ view->utf8 = (gboolean) str_isutf8 (cp_id);
+ view->dpy_wrap_dirty = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_select_encoding (WView * view)
+{
+ if (do_select_codepage ())
+ mcview_set_codeset (view);
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_show_error (WView * view, const char *msg)
+{
+ if (mcview_is_in_panel (view))
+ mcview_set_datasource_string (view, msg);
+ else
+ message (D_ERROR, MSG_ERROR, "%s", msg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns index of the first char in the line
+ * it is constant for all line characters
+ */
+
+off_t
+mcview_bol (WView * view, off_t current, off_t limit)
+{
+ int c;
+ off_t filesize;
+ filesize = mcview_get_filesize (view);
+ if (current <= 0)
+ return 0;
+ if (current > filesize)
+ return filesize;
+ if (!mcview_get_byte (view, current, &c))
+ return current;
+ if (c == '\n')
+ {
+ if (!mcview_get_byte (view, current - 1, &c))
+ return current;
+ if (c == '\r')
+ current--;
+ }
+ while (current > 0 && current > limit)
+ {
+ if (!mcview_get_byte (view, current - 1, &c))
+ break;
+ if (c == '\r' || c == '\n')
+ break;
+ current--;
+ }
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns index of last char on line + width EOL
+ * mcview_eol of the current line == mcview_bol next line
+ */
+
+off_t
+mcview_eol (WView * view, off_t current)
+{
+ int c, prev_ch = 0;
+
+ if (current < 0)
+ return 0;
+
+ while (TRUE)
+ {
+ if (!mcview_get_byte (view, current, &c))
+ break;
+ if (c == '\n')
+ {
+ current++;
+ break;
+ }
+ else if (prev_ch == '\r')
+ {
+ break;
+ }
+ current++;
+ prev_ch = c;
+ }
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mcview_get_title (const WDialog * h, size_t len)
+{
+ const WView *view;
+ const char *modified;
+ const char *file_label;
+ const char *view_filename;
+ char *ret_str;
+
+ view = (const WView *) widget_find_by_type (CONST_WIDGET (h), mcview_callback);
+ modified = view->hexedit_mode && (view->change_list != NULL) ? "(*) " : " ";
+ view_filename = vfs_path_as_str (view->filename_vpath);
+
+ len -= 4;
+
+ file_label = view_filename != NULL ? view_filename : view->command != NULL ? view->command : "";
+ file_label = str_term_trim (file_label, len - str_term_width1 (_("View: ")));
+
+ ret_str = g_strconcat (_("View: "), modified, file_label, (char *) NULL);
+ return ret_str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mcview_calc_percent (WView * view, off_t p)
+{
+ off_t filesize;
+ int percent;
+
+ if (view->status_area.cols < 1 || (view->status_area.x + view->status_area.cols) < 4)
+ return (-1);
+ if (mcview_may_still_grow (view))
+ return (-1);
+
+ filesize = mcview_get_filesize (view);
+ if (view->mode_flags.hex && filesize > 0)
+ {
+ /* p can't be beyond the last char, only over that. Compensate for this. */
+ filesize--;
+ }
+
+ if (filesize == 0 || p >= filesize)
+ percent = 100;
+ else if (p > (INT_MAX / 100))
+ percent = p / (filesize / 100);
+ else
+ percent = p * 100 / filesize;
+
+ return percent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_clear_mode_flags (mcview_mode_flags_t * flags)
+{
+ memset (flags, 0, sizeof (*flags));
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/mcviewer.c b/src/viewer/mcviewer.c
new file mode 100644
index 0000000..36d31c0
--- /dev/null
+++ b/src/viewer/mcviewer.c
@@ -0,0 +1,469 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Interface functions
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* load_file_position() */
+#include "lib/widget.h"
+
+#include "src/filemanager/layout.h"
+#include "src/filemanager/filemanager.h" /* the_menubar */
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+mcview_mode_flags_t mcview_global_flags = {
+ .wrap = TRUE,
+ .hex = FALSE,
+ .magic = TRUE,
+ .nroff = FALSE
+};
+
+mcview_mode_flags_t mcview_altered_flags = {
+ .wrap = FALSE,
+ .hex = FALSE,
+ .magic = FALSE,
+ .nroff = FALSE
+};
+
+gboolean mcview_remember_file_position = FALSE;
+
+/* Maxlimit for skipping updates */
+int mcview_max_dirt_limit = 10;
+
+/* Scrolling is done in pages or line increments */
+gboolean mcview_mouse_move_pages = TRUE;
+
+/* end of file will be showen from mcview_show_eof */
+char *mcview_show_eof = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WView *view = (WView *) w;
+ const WRect *r = &view->data_area;
+ gboolean ok = TRUE;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ if (mcview_is_in_panel (view))
+ {
+ if (event->y == WIDGET (w->owner)->rect.y)
+ {
+ /* return MOU_UNHANDLED */
+ event->result.abort = TRUE;
+ /* don't draw viewer over menu */
+ ok = FALSE;
+ break;
+ }
+
+ if (!widget_get_state (w, WST_FOCUSED))
+ {
+ /* Grab focus */
+ (void) change_panel ();
+ }
+ }
+ MC_FALLTHROUGH;
+
+ case MSG_MOUSE_CLICK:
+ if (!view->mode_flags.wrap)
+ {
+ /* Scrolling left and right */
+ int x;
+
+ x = event->x + 1; /* FIXME */
+
+ if (x < r->cols * 1 / 4)
+ {
+ mcview_move_left (view, 1);
+ event->result.repeat = msg == MSG_MOUSE_DOWN;
+ }
+ else if (x < r->cols * 3 / 4)
+ {
+ /* ignore the click */
+ ok = FALSE;
+ }
+ else
+ {
+ mcview_move_right (view, 1);
+ event->result.repeat = msg == MSG_MOUSE_DOWN;
+ }
+ }
+ else
+ {
+ /* Scrolling up and down */
+ int y;
+
+ y = event->y + 1; /* FIXME */
+
+ if (y < r->y + r->lines * 1 / 3)
+ {
+ if (mcview_mouse_move_pages)
+ mcview_move_up (view, r->lines / 2);
+ else
+ mcview_move_up (view, 1);
+
+ event->result.repeat = msg == MSG_MOUSE_DOWN;
+ }
+ else if (y < r->y + r->lines * 2 / 3)
+ {
+ /* ignore the click */
+ ok = FALSE;
+ }
+ else
+ {
+ if (mcview_mouse_move_pages)
+ mcview_move_down (view, r->lines / 2);
+ else
+ mcview_move_down (view, 1);
+
+ event->result.repeat = msg == MSG_MOUSE_DOWN;
+ }
+ }
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ mcview_move_up (view, 2);
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ mcview_move_down (view, 2);
+ break;
+
+ default:
+ ok = FALSE;
+ break;
+ }
+
+ if (ok)
+ mcview_update (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WView *
+mcview_new (int y, int x, int lines, int cols, gboolean is_panel)
+{
+ WRect r = { y, x, lines, cols };
+ WView *view;
+ Widget *w;
+
+ view = g_new0 (WView, 1);
+ w = WIDGET (view);
+
+ widget_init (w, &r, mcview_callback, mcview_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
+ w->keymap = viewer_map;
+
+ mcview_clear_mode_flags (&view->mode_flags);
+ view->hexedit_mode = FALSE;
+ view->hex_keymap = viewer_hex_map;
+ view->hexview_in_text = FALSE;
+ view->locked = FALSE;
+
+ view->dpy_frame_size = is_panel ? 1 : 0;
+ view->converter = str_cnv_from_term;
+
+ mcview_init (view);
+
+ if (mcview_global_flags.hex)
+ mcview_toggle_hex_mode (view);
+ if (mcview_global_flags.nroff)
+ mcview_toggle_nroff_mode (view);
+ if (mcview_global_flags.wrap)
+ mcview_toggle_wrap_mode (view);
+ if (mcview_global_flags.magic)
+ mcview_toggle_magic_mode (view);
+
+ return view;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Real view only */
+
+gboolean
+mcview_viewer (const char *command, const vfs_path_t * file_vpath, int start_line,
+ off_t search_start, off_t search_end)
+{
+ gboolean succeeded;
+ WView *lc_mcview;
+ WDialog *view_dlg;
+ Widget *vw, *b;
+ WGroup *g;
+
+ /* Create dialog and widgets, put them on the dialog */
+ view_dlg = dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, mcview_dialog_callback,
+ NULL, "[Internal File Viewer]", NULL);
+ vw = WIDGET (view_dlg);
+ widget_want_tab (vw, TRUE);
+
+ g = GROUP (view_dlg);
+
+ lc_mcview = mcview_new (vw->rect.y, vw->rect.x, vw->rect.lines - 1, vw->rect.cols, FALSE);
+ group_add_widget_autopos (g, lc_mcview, WPOS_KEEP_ALL, NULL);
+
+ b = WIDGET (buttonbar_new ());
+ group_add_widget_autopos (g, b, b->pos_flags, NULL);
+
+ view_dlg->get_title = mcview_get_title;
+
+ succeeded =
+ mcview_load (lc_mcview, command, vfs_path_as_str (file_vpath), start_line, search_start,
+ search_end);
+
+ if (succeeded)
+ dlg_run (view_dlg);
+ else
+ dlg_close (view_dlg);
+
+ if (widget_get_state (vw, WST_CLOSED))
+ widget_destroy (vw);
+
+ return succeeded;
+}
+
+/* {{{ Miscellaneous functions }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_load (WView * view, const char *command, const char *file, int start_line,
+ off_t search_start, off_t search_end)
+{
+ gboolean retval = FALSE;
+ vfs_path_t *vpath = NULL;
+
+ g_assert (view->bytes_per_line != 0);
+
+ view->filename_vpath = vfs_path_from_str (file);
+
+ /* get working dir */
+ if (file != NULL && file[0] != '\0')
+ {
+ vfs_path_free (view->workdir_vpath, TRUE);
+
+ if (!g_path_is_absolute (file))
+ {
+ vfs_path_t *p;
+
+ p = vfs_path_clone (vfs_get_raw_current_dir ());
+ view->workdir_vpath = vfs_path_append_new (p, file, (char *) NULL);
+ vfs_path_free (p, TRUE);
+ }
+ else
+ {
+ /* try extract path from filename */
+ const char *fname;
+ char *dir;
+
+ fname = x_basename (file);
+ dir = g_strndup (file, (size_t) (fname - file));
+ view->workdir_vpath = vfs_path_from_str (dir);
+ g_free (dir);
+ }
+ }
+
+ if (!mcview_is_in_panel (view))
+ view->dpy_text_column = 0;
+
+#ifdef HAVE_CHARSET
+ mcview_set_codeset (view);
+#endif
+
+ if (command != NULL && (view->mode_flags.magic || file == NULL || file[0] == '\0'))
+ retval = mcview_load_command_output (view, command);
+ else if (file != NULL && file[0] != '\0')
+ {
+ int fd;
+ char tmp[BUF_MEDIUM];
+ struct stat st;
+
+ /* Open the file */
+ vpath = vfs_path_from_str (file);
+ fd = mc_open (vpath, O_RDONLY | O_NONBLOCK);
+ if (fd == -1)
+ {
+ g_snprintf (tmp, sizeof (tmp), _("Cannot open \"%s\"\n%s"),
+ file, unix_error_string (errno));
+ mcview_close_datasource (view);
+ mcview_show_error (view, tmp);
+ vfs_path_free (view->filename_vpath, TRUE);
+ view->filename_vpath = NULL;
+ vfs_path_free (view->workdir_vpath, TRUE);
+ view->workdir_vpath = NULL;
+ goto finish;
+ }
+
+ /* Make sure we are working with a regular file */
+ if (mc_fstat (fd, &st) == -1)
+ {
+ mc_close (fd);
+ g_snprintf (tmp, sizeof (tmp), _("Cannot stat \"%s\"\n%s"),
+ file, unix_error_string (errno));
+ mcview_close_datasource (view);
+ mcview_show_error (view, tmp);
+ vfs_path_free (view->filename_vpath, TRUE);
+ view->filename_vpath = NULL;
+ vfs_path_free (view->workdir_vpath, TRUE);
+ view->workdir_vpath = NULL;
+ goto finish;
+ }
+
+ if (!S_ISREG (st.st_mode))
+ {
+ mc_close (fd);
+ mcview_close_datasource (view);
+ mcview_show_error (view, _("Cannot view: not a regular file"));
+ vfs_path_free (view->filename_vpath, TRUE);
+ view->filename_vpath = NULL;
+ vfs_path_free (view->workdir_vpath, TRUE);
+ view->workdir_vpath = NULL;
+ goto finish;
+ }
+
+ if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1)
+ {
+ /* Must be one of those nice files that grow (/proc) */
+ mcview_set_datasource_vfs_pipe (view, fd);
+ }
+ else
+ {
+ if (view->mode_flags.magic)
+ {
+ int type;
+
+ type = get_compression_type (fd, file);
+
+ if (type != COMPRESSION_NONE)
+ {
+ char *tmp_filename;
+ vfs_path_t *vpath1;
+ int fd1;
+
+ tmp_filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
+ vpath1 = vfs_path_from_str (tmp_filename);
+ g_free (tmp_filename);
+ fd1 = mc_open (vpath1, O_RDONLY | O_NONBLOCK);
+ vfs_path_free (vpath1, TRUE);
+
+ if (fd1 == -1)
+ {
+ g_snprintf (tmp, sizeof (tmp), _("Cannot open \"%s\" in parse mode\n%s"),
+ file, unix_error_string (errno));
+ mcview_close_datasource (view);
+ mcview_show_error (view, tmp);
+ }
+ else
+ {
+ mc_close (fd);
+ fd = fd1;
+ mc_fstat (fd, &st);
+ }
+ }
+ }
+
+ mcview_set_datasource_file (view, fd, &st);
+ }
+ retval = TRUE;
+ }
+
+ finish:
+ view->command = g_strdup (command);
+ view->dpy_start = 0;
+ view->dpy_paragraph_skip_lines = 0;
+ mcview_state_machine_init (&view->dpy_state_top, 0);
+ view->dpy_wrap_dirty = FALSE;
+ view->force_max = -1;
+ view->dpy_text_column = 0;
+
+ mcview_compute_areas (view);
+ mcview_update_bytes_per_line (view);
+
+ if (mcview_remember_file_position && view->filename_vpath != NULL && start_line == 0)
+ {
+ long line, col;
+ off_t new_offset, max_offset;
+
+ load_file_position (view->filename_vpath, &line, &col, &new_offset, &view->saved_bookmarks);
+ max_offset = mcview_get_filesize (view) - 1;
+ if (max_offset < 0)
+ new_offset = 0;
+ else
+ new_offset = MIN (new_offset, max_offset);
+ if (!view->mode_flags.hex)
+ {
+ view->dpy_start = mcview_bol (view, new_offset, 0);
+ view->dpy_wrap_dirty = TRUE;
+ }
+ else
+ {
+ view->dpy_start = new_offset - new_offset % view->bytes_per_line;
+ view->hex_cursor = new_offset;
+ }
+ }
+ else if (start_line > 0)
+ mcview_moveto (view, start_line - 1, 0);
+
+ view->search_start = search_start;
+ view->search_end = search_end;
+ view->hexedit_lownibble = FALSE;
+ view->hexview_in_text = FALSE;
+ view->change_list = NULL;
+ vfs_path_free (vpath, TRUE);
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/mcviewer.h b/src/viewer/mcviewer.h
new file mode 100644
index 0000000..d90716c
--- /dev/null
+++ b/src/viewer/mcviewer.h
@@ -0,0 +1,57 @@
+/** \file mcviewer.h
+ * \brief Header: internal file viewer
+ */
+
+#ifndef MC__VIEWER_H
+#define MC__VIEWER_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct WView;
+typedef struct WView WView;
+
+typedef struct
+{
+ gboolean wrap; /* Wrap text lines to fit them on the screen */
+ gboolean hex; /* Plainview or hexview */
+ gboolean magic; /* Preprocess the file using external programs */
+ gboolean nroff; /* Nroff-style highlighting */
+} mcview_mode_flags_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern mcview_mode_flags_t mcview_global_flags;
+extern mcview_mode_flags_t mcview_altered_flags;
+
+extern gboolean mcview_remember_file_position;
+extern int mcview_max_dirt_limit;
+
+extern gboolean mcview_mouse_move_pages;
+extern char *mcview_show_eof;
+
+/*** declarations of public functions ************************************************************/
+
+/* Creates a new WView object with the given properties. Caveat: the
+ * origin is in y-x order, while the extent is in x-y order. */
+extern WView *mcview_new (int y, int x, int lines, int cols, gboolean is_panel);
+
+
+/* Shows {file} or the output of {command} in the internal viewer,
+ * starting in line {start_line}.
+ */
+extern gboolean mcview_viewer (const char *command, const vfs_path_t * file_vpath, int start_line,
+ off_t search_start, off_t search_end);
+
+extern gboolean mcview_load (WView * view, const char *command, const char *file, int start_line,
+ off_t search_start, off_t search_end);
+
+extern void mcview_clear_mode_flags (mcview_mode_flags_t * flags);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__VIEWER_H */
diff --git a/src/viewer/move.c b/src/viewer/move.c
new file mode 100644
index 0000000..4f15b7c
--- /dev/null
+++ b/src/viewer/move.c
@@ -0,0 +1,415 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Functions for handle cursor movement
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009, 2010
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ The following variables have to do with the current position and are
+ updated by the cursor movement functions.
+
+ In hex view and wrapped text view mode, dpy_start marks the offset of
+ the top-left corner on the screen, in non-wrapping text mode it is
+ the beginning of the current line. In hex mode, hex_cursor is the
+ offset of the cursor. In non-wrapping text mode, dpy_text_column is
+ the number of columns that are hidden on the left side on the screen.
+
+ In hex mode, dpy_start is updated by the view_fix_cursor_position()
+ function in order to keep the other functions simple. In
+ non-wrapping text mode dpy_start and dpy_text_column are normalized
+ such that dpy_text_column < view_get_datacolumns().
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_scroll_to_cursor (WView * view)
+{
+ if (view->mode_flags.hex)
+ {
+ off_t bytes = view->bytes_per_line;
+ off_t cursor = view->hex_cursor;
+ off_t topleft = view->dpy_start;
+ off_t displaysize;
+
+ displaysize = view->data_area.lines * bytes;
+ if (topleft + displaysize <= cursor)
+ topleft = mcview_offset_rounddown (cursor, bytes) - (displaysize - bytes);
+ if (cursor < topleft)
+ topleft = mcview_offset_rounddown (cursor, bytes);
+ view->dpy_start = topleft;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_movement_fixups (WView * view, gboolean reset_search)
+{
+ mcview_scroll_to_cursor (view);
+
+ if (reset_search)
+ {
+ view->search_start = view->mode_flags.hex ? view->hex_cursor : view->dpy_start;
+ view->search_end = view->search_start;
+ }
+
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_move_up (WView * view, off_t lines)
+{
+ if (!view->mode_flags.hex)
+ mcview_ascii_move_up (view, lines);
+ else
+ {
+ off_t bytes;
+
+ bytes = lines * view->bytes_per_line;
+
+ if (view->hex_cursor < bytes)
+ view->hex_cursor %= view->bytes_per_line;
+ else
+ {
+ view->hex_cursor -= bytes;
+ if (view->hex_cursor < view->dpy_start)
+ {
+ view->dpy_start = DOZ (view->dpy_start, bytes);
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ }
+ }
+
+ mcview_movement_fixups (view, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_move_down (WView * view, off_t lines)
+{
+ off_t last_byte;
+
+ last_byte = mcview_get_filesize (view);
+
+ if (!view->mode_flags.hex)
+ mcview_ascii_move_down (view, lines);
+ else
+ {
+ off_t i, limit;
+
+ limit = DOZ (last_byte, (off_t) view->bytes_per_line);
+
+ for (i = 0; i < lines && view->hex_cursor < limit; i++)
+ {
+ view->hex_cursor += view->bytes_per_line;
+
+ if (lines != 1)
+ {
+ view->dpy_start += view->bytes_per_line;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ }
+ }
+
+ mcview_movement_fixups (view, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_move_left (WView * view, off_t columns)
+{
+ if (view->mode_flags.hex)
+ {
+ off_t old_cursor = view->hex_cursor;
+
+ g_assert (columns == 1);
+
+ if (view->hexview_in_text || !view->hexedit_lownibble)
+ if (view->hex_cursor > 0)
+ view->hex_cursor--;
+
+ if (!view->hexview_in_text)
+ if (old_cursor > 0 || view->hexedit_lownibble)
+ view->hexedit_lownibble = !view->hexedit_lownibble;
+ }
+ else if (!view->mode_flags.wrap)
+ view->dpy_text_column = DOZ (view->dpy_text_column, columns);
+
+ mcview_movement_fixups (view, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_move_right (WView * view, off_t columns)
+{
+ if (view->mode_flags.hex)
+ {
+ off_t last_byte;
+ off_t old_cursor = view->hex_cursor;
+
+ last_byte = mcview_get_filesize (view);
+ last_byte = DOZ (last_byte, 1);
+
+ g_assert (columns == 1);
+
+ if (view->hexview_in_text || view->hexedit_lownibble)
+ if (view->hex_cursor < last_byte)
+ view->hex_cursor++;
+
+ if (!view->hexview_in_text)
+ if (old_cursor < last_byte || !view->hexedit_lownibble)
+ view->hexedit_lownibble = !view->hexedit_lownibble;
+ }
+ else if (!view->mode_flags.wrap)
+ view->dpy_text_column += columns;
+
+ mcview_movement_fixups (view, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto_top (WView * view)
+{
+ view->dpy_start = 0;
+ view->dpy_paragraph_skip_lines = 0;
+ mcview_state_machine_init (&view->dpy_state_top, 0);
+ view->hex_cursor = 0;
+ view->dpy_text_column = 0;
+ mcview_movement_fixups (view, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto_bottom (WView * view)
+{
+ off_t filesize;
+
+ mcview_update_filesize (view);
+
+ if (view->growbuf_in_use)
+ mcview_growbuf_read_all_data (view);
+
+ filesize = mcview_get_filesize (view);
+
+ if (view->mode_flags.hex)
+ {
+ view->hex_cursor = DOZ (filesize, 1);
+ mcview_movement_fixups (view, TRUE);
+ }
+ else
+ {
+ view->dpy_start = filesize;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ mcview_move_up (view, view->data_area.lines);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto_bol (WView * view)
+{
+ if (!view->mode_flags.hex)
+ mcview_ascii_moveto_bol (view);
+ else
+ {
+ view->hex_cursor -= view->hex_cursor % view->bytes_per_line;
+ view->dpy_text_column = 0;
+ }
+
+ mcview_movement_fixups (view, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto_eol (WView * view)
+{
+ off_t bol;
+
+ if (!view->mode_flags.hex)
+ mcview_ascii_moveto_eol (view);
+ else
+ {
+ off_t filesize;
+
+ bol = mcview_offset_rounddown (view->hex_cursor, view->bytes_per_line);
+
+ if (mcview_get_byte_indexed (view, bol, view->bytes_per_line - 1, NULL))
+ view->hex_cursor = bol + view->bytes_per_line - 1;
+ else
+ {
+ filesize = mcview_get_filesize (view);
+ view->hex_cursor = DOZ (filesize, 1);
+ }
+ }
+
+ mcview_movement_fixups (view, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto_offset (WView * view, off_t offset)
+{
+ if (view->mode_flags.hex)
+ {
+ view->hex_cursor = offset;
+ view->dpy_start = offset - offset % view->bytes_per_line;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ else
+ {
+ view->dpy_start = offset;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+
+ mcview_movement_fixups (view, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto (WView * view, off_t line, off_t col)
+{
+ off_t offset;
+
+ mcview_coord_to_offset (view, &offset, line, col);
+ mcview_moveto_offset (view, offset);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_coord_to_offset (WView * view, off_t * ret_offset, off_t line, off_t column)
+{
+ coord_cache_entry_t coord;
+
+ coord.cc_line = line;
+ coord.cc_column = column;
+ coord.cc_nroff_column = column;
+ mcview_ccache_lookup (view, &coord, CCACHE_OFFSET);
+ *ret_offset = coord.cc_offset;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_offset_to_coord (WView * view, off_t * ret_line, off_t * ret_column, off_t offset)
+{
+ coord_cache_entry_t coord;
+
+ coord.cc_offset = offset;
+ mcview_ccache_lookup (view, &coord, CCACHE_LINECOL);
+
+ *ret_line = coord.cc_line;
+ *ret_column = view->mode_flags.nroff ? coord.cc_nroff_column : coord.cc_column;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_place_cursor (WView * view)
+{
+ const WRect *r = &view->data_area;
+ int col = view->cursor_col;
+
+ if (!view->hexview_in_text && view->hexedit_lownibble)
+ col++;
+
+ widget_gotoyx (view, r->y + view->cursor_row, r->x + col);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** we have set view->search_start and view->search_end and must set
+ * view->dpy_text_column and view->dpy_start
+ * try to display maximum of match */
+
+void
+mcview_moveto_match (WView * view)
+{
+ if (view->mode_flags.hex)
+ {
+ view->hex_cursor = view->search_start;
+ view->hexedit_lownibble = FALSE;
+ view->dpy_start = view->search_start - view->search_start % view->bytes_per_line;
+ view->dpy_end = view->search_end - view->search_end % view->bytes_per_line;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ else
+ {
+ view->dpy_start = mcview_bol (view, view->search_start, 0);
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+
+ mcview_scroll_to_cursor (view);
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/nroff.c b/src/viewer/nroff.c
new file mode 100644
index 0000000..14dacd5
--- /dev/null
+++ b/src/viewer/nroff.c
@@ -0,0 +1,287 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Functions for searching in nroff-like view
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_nroff_get_char (mcview_nroff_t * nroff, int *ret_val, off_t nroff_index)
+{
+ int c = 0;
+
+#ifdef HAVE_CHARSET
+ if (nroff->view->utf8)
+ {
+ if (!mcview_get_utf (nroff->view, nroff_index, &c, &nroff->char_length))
+ {
+ /* we need got symbol in any case */
+ nroff->char_length = 1;
+ if (!mcview_get_byte (nroff->view, nroff_index, &c) || !g_ascii_isprint (c))
+ return FALSE;
+ }
+ }
+ else
+#endif
+ {
+ nroff->char_length = 1;
+ if (!mcview_get_byte (nroff->view, nroff_index, &c))
+ return FALSE;
+ }
+
+ *ret_val = c;
+
+ return g_unichar_isprint (c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mcview__get_nroff_real_len (WView * view, off_t start, off_t length)
+{
+ mcview_nroff_t *nroff;
+ int ret = 0;
+ off_t i = 0;
+
+ if (!view->mode_flags.nroff)
+ return 0;
+
+ nroff = mcview_nroff_seq_new_num (view, start);
+ if (nroff == NULL)
+ return 0;
+ while (i < length)
+ {
+ switch (nroff->type)
+ {
+ case NROFF_TYPE_BOLD:
+ ret += 1 + nroff->char_length; /* real char length and 0x8 */
+ break;
+ case NROFF_TYPE_UNDERLINE:
+ ret += 2; /* underline symbol and ox8 */
+ break;
+ default:
+ break;
+ }
+ i += nroff->char_length;
+ mcview_nroff_seq_next (nroff);
+ }
+
+ mcview_nroff_seq_free (&nroff);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mcview_nroff_t *
+mcview_nroff_seq_new_num (WView * view, off_t lc_index)
+{
+ mcview_nroff_t *nroff;
+
+ nroff = g_try_malloc0 (sizeof (mcview_nroff_t));
+ if (nroff != NULL)
+ {
+ nroff->index = lc_index;
+ nroff->view = view;
+ mcview_nroff_seq_info (nroff);
+ }
+ return nroff;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mcview_nroff_t *
+mcview_nroff_seq_new (WView * view)
+{
+ return mcview_nroff_seq_new_num (view, (off_t) 0);
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_nroff_seq_free (mcview_nroff_t ** nroff)
+{
+ if (nroff == NULL || *nroff == NULL)
+ return;
+ MC_PTR_FREE (*nroff);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+nroff_type_t
+mcview_nroff_seq_info (mcview_nroff_t * nroff)
+{
+ int next, next2;
+
+ if (nroff == NULL)
+ return NROFF_TYPE_NONE;
+ nroff->type = NROFF_TYPE_NONE;
+
+ if (!mcview_nroff_get_char (nroff, &nroff->current_char, nroff->index))
+ return nroff->type;
+
+ if (!mcview_get_byte (nroff->view, nroff->index + nroff->char_length, &next) || next != '\b')
+ return nroff->type;
+
+ if (!mcview_nroff_get_char (nroff, &next2, nroff->index + 1 + nroff->char_length))
+ return nroff->type;
+
+ if (nroff->current_char == '_' && next2 == '_')
+ {
+ nroff->type = (nroff->prev_type == NROFF_TYPE_BOLD)
+ ? NROFF_TYPE_BOLD : NROFF_TYPE_UNDERLINE;
+
+ }
+ else if (nroff->current_char == next2)
+ {
+ nroff->type = NROFF_TYPE_BOLD;
+ }
+ else if (nroff->current_char == '_')
+ {
+ nroff->current_char = next2;
+ nroff->type = NROFF_TYPE_UNDERLINE;
+ }
+ else if (nroff->current_char == '+' && next2 == 'o')
+ {
+ /* ??? */
+ }
+ return nroff->type;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mcview_nroff_seq_next (mcview_nroff_t * nroff)
+{
+ if (nroff == NULL)
+ return -1;
+
+ nroff->prev_type = nroff->type;
+
+ switch (nroff->type)
+ {
+ case NROFF_TYPE_BOLD:
+ nroff->index += 1 + nroff->char_length;
+ break;
+ case NROFF_TYPE_UNDERLINE:
+ nroff->index += 2;
+ break;
+ default:
+ break;
+ }
+
+ nroff->index += nroff->char_length;
+
+ mcview_nroff_seq_info (nroff);
+ return nroff->current_char;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mcview_nroff_seq_prev (mcview_nroff_t * nroff)
+{
+ int prev;
+ off_t prev_index, prev_index2;
+
+ if (nroff == NULL)
+ return -1;
+
+ nroff->prev_type = NROFF_TYPE_NONE;
+
+ if (nroff->index == 0)
+ return -1;
+
+ prev_index = nroff->index - 1;
+
+ while (prev_index != 0)
+ {
+ if (mcview_nroff_get_char (nroff, &nroff->current_char, prev_index))
+ break;
+ prev_index--;
+ }
+ if (prev_index == 0)
+ {
+ nroff->index--;
+ mcview_nroff_seq_info (nroff);
+ return nroff->current_char;
+ }
+
+ prev_index--;
+
+ if (!mcview_get_byte (nroff->view, prev_index, &prev) || prev != '\b')
+ {
+ nroff->index = prev_index;
+ mcview_nroff_seq_info (nroff);
+ return nroff->current_char;
+ }
+ prev_index2 = prev_index - 1;
+
+ while (prev_index2 != 0)
+ {
+ if (mcview_nroff_get_char (nroff, &prev, prev_index))
+ break;
+ prev_index2--;
+ }
+
+ nroff->index = (prev_index2 == 0) ? prev_index : prev_index2;
+ mcview_nroff_seq_info (nroff);
+ return nroff->current_char;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/search.c b/src/viewer/search.c
new file mode 100644
index 0000000..f470a36
--- /dev/null
+++ b/src/viewer/search.c
@@ -0,0 +1,491 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for search data
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* cp_source */
+#endif
+#include "lib/widget.h"
+
+#include "src/setup.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+mcview_search_options_t mcview_search_options = {
+ .type = MC_SEARCH_T_NORMAL,
+ .case_sens = FALSE,
+ .backwards = FALSE,
+ .whole_words = FALSE,
+ .all_codepages = FALSE
+};
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ simple_status_msg_t status_msg; /* base class */
+
+ gboolean first;
+ WView *view;
+ off_t offset;
+} mcview_search_status_msg_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static int search_cb_char_curr_index = -1;
+static char search_cb_char_buffer[6];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mcview_search_status_update_cb (status_msg_t * sm)
+{
+ simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
+ mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) sm;
+ Widget *wd = WIDGET (sm->dlg);
+ int percent = -1;
+
+ if (verbose)
+ percent = mcview_calc_percent (vsm->view, vsm->offset);
+
+ if (percent >= 0)
+ label_set_textv (ssm->label, _("Searching %s: %3d%%"), vsm->view->last_search_string,
+ percent);
+ else
+ label_set_textv (ssm->label, _("Searching %s"), vsm->view->last_search_string);
+
+ if (vsm->first)
+ {
+ Widget *lw = WIDGET (ssm->label);
+ WRect r;
+
+ r = wd->rect;
+ r.cols = MAX (r.cols, lw->rect.cols + 6);
+ widget_set_size_rect (wd, &r);
+ r = lw->rect;
+ r.x = wd->rect.x + (wd->rect.cols - r.cols) / 2;
+ widget_set_size_rect (lw, &r);
+ vsm->first = FALSE;
+ }
+
+ return status_msg_common_update (sm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_search_update_steps (WView * view)
+{
+ off_t filesize;
+
+ filesize = mcview_get_filesize (view);
+
+ if (filesize != 0)
+ view->update_steps = filesize / 100;
+ else /* viewing a data stream, not a file */
+ view->update_steps = 40000;
+
+ /* Do not update the percent display but every 20 kb */
+ if (view->update_steps < 20000)
+ view->update_steps = 20000;
+
+ /* Make interrupt more responsive */
+ if (view->update_steps > 40000)
+ view->update_steps = 40000;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_find (mcview_search_status_msg_t * ssm, off_t search_start, off_t search_end, gsize * len)
+{
+ WView *view = ssm->view;
+
+ view->search_numNeedSkipChar = 0;
+ search_cb_char_curr_index = -1;
+
+ if (mcview_search_options.backwards)
+ {
+ search_end = mcview_get_filesize (view);
+ while (search_start >= 0)
+ {
+ gboolean ok;
+
+ view->search_nroff_seq->index = search_start;
+ mcview_nroff_seq_info (view->search_nroff_seq);
+
+ if (search_end > search_start + (off_t) view->search->original.str->len
+ && mc_search_is_fixed_search_str (view->search))
+ search_end = search_start + view->search->original.str->len;
+
+ ok = mc_search_run (view->search, (void *) ssm, search_start, search_end, len);
+ if (ok && view->search->normal_offset == search_start)
+ {
+ if (view->mode_flags.nroff)
+ view->search->normal_offset++;
+ return TRUE;
+ }
+
+ /* We abort the search in case of a pattern error, or if the user aborts
+ the search. In other words: in all cases except "string not found". */
+ if (!ok && view->search->error != MC_SEARCH_E_NOTFOUND)
+ return FALSE;
+
+ search_start--;
+ }
+
+ mc_search_set_error (view->search, MC_SEARCH_E_NOTFOUND, "%s", _(STR_E_NOTFOUND));
+ return FALSE;
+ }
+ view->search_nroff_seq->index = search_start;
+ mcview_nroff_seq_info (view->search_nroff_seq);
+
+ return mc_search_run (view->search, (void *) ssm, search_start, search_end, len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_search_show_result (WView * view, size_t match_len)
+{
+ int nroff_len;
+
+ nroff_len =
+ view->mode_flags.nroff
+ ? mcview__get_nroff_real_len (view, view->search->start_buffer,
+ view->search->normal_offset - view->search->start_buffer) : 0;
+ view->search_start = view->search->normal_offset + nroff_len;
+
+ if (!view->mode_flags.hex)
+ view->search_start++;
+
+ nroff_len =
+ view->mode_flags.nroff ? mcview__get_nroff_real_len (view, view->search_start - 1,
+ match_len) : 0;
+ view->search_end = view->search_start + match_len + nroff_len;
+
+ mcview_moveto_match (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_search_init (WView * view)
+{
+#ifdef HAVE_CHARSET
+ view->search = mc_search_new (view->last_search_string, cp_source);
+#else
+ view->search = mc_search_new (view->last_search_string, NULL);
+#endif
+
+ view->search_nroff_seq = mcview_nroff_seq_new (view);
+
+ if (view->search == NULL)
+ return FALSE;
+
+ view->search->search_type = mcview_search_options.type;
+#ifdef HAVE_CHARSET
+ view->search->is_all_charsets = mcview_search_options.all_codepages;
+#endif
+ view->search->is_case_sensitive = mcview_search_options.case_sens;
+ view->search->whole_words = mcview_search_options.whole_words;
+ view->search->search_fn = mcview_search_cmd_callback;
+ view->search->update_fn = mcview_search_update_cmd_callback;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_search_deinit (WView * view)
+{
+ mc_search_free (view->search);
+ g_free (view->last_search_string);
+ mcview_nroff_seq_free (&view->search_nroff_seq);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mc_search_cbret_t
+mcview_search_cmd_callback (const void *user_data, gsize char_offset, int *current_char)
+{
+ WView *view = ((const mcview_search_status_msg_t *) user_data)->view;
+
+ /* view_read_continue (view, &view->search_onechar_info); *//* AB:FIXME */
+ if (!view->mode_flags.nroff)
+ {
+ mcview_get_byte (view, char_offset, current_char);
+ return MC_SEARCH_CB_OK;
+ }
+
+ if (view->search_numNeedSkipChar != 0)
+ {
+ view->search_numNeedSkipChar--;
+ return MC_SEARCH_CB_SKIP;
+ }
+
+ if (search_cb_char_curr_index == -1
+ || search_cb_char_curr_index >= view->search_nroff_seq->char_length)
+ {
+ if (search_cb_char_curr_index != -1)
+ mcview_nroff_seq_next (view->search_nroff_seq);
+
+ search_cb_char_curr_index = 0;
+ if (view->search_nroff_seq->char_length > 1)
+ g_unichar_to_utf8 (view->search_nroff_seq->current_char, search_cb_char_buffer);
+ else
+ search_cb_char_buffer[0] = (char) view->search_nroff_seq->current_char;
+
+ if (view->search_nroff_seq->type != NROFF_TYPE_NONE)
+ {
+ switch (view->search_nroff_seq->type)
+ {
+ case NROFF_TYPE_BOLD:
+ view->search_numNeedSkipChar = 1 + view->search_nroff_seq->char_length; /* real char length and 0x8 */
+ break;
+ case NROFF_TYPE_UNDERLINE:
+ view->search_numNeedSkipChar = 2; /* underline symbol and ox8 */
+ break;
+ default:
+ break;
+ }
+ }
+ return MC_SEARCH_CB_INVALID;
+ }
+
+ *current_char = search_cb_char_buffer[search_cb_char_curr_index];
+ search_cb_char_curr_index++;
+
+ return (*current_char != -1) ? MC_SEARCH_CB_OK : MC_SEARCH_CB_INVALID;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mc_search_cbret_t
+mcview_search_update_cmd_callback (const void *user_data, gsize char_offset)
+{
+ status_msg_t *sm = STATUS_MSG (user_data);
+ mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) user_data;
+ WView *view = vsm->view;
+ gboolean do_update = FALSE;
+ mc_search_cbret_t result = MC_SEARCH_CB_OK;
+
+ vsm->offset = (off_t) char_offset;
+
+ if (mcview_search_options.backwards)
+ {
+ if (vsm->offset <= view->update_activate)
+ {
+ view->update_activate -= view->update_steps;
+
+ do_update = TRUE;
+ }
+ }
+ else
+ {
+ if (vsm->offset >= view->update_activate)
+ {
+ view->update_activate += view->update_steps;
+
+ do_update = TRUE;
+ }
+ }
+
+ if (do_update && sm->update (sm) == B_CANCEL)
+ result = MC_SEARCH_CB_ABORT;
+
+ /* may be in future return from this callback will change current position in searching block. */
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_do_search (WView * view, off_t want_search_start)
+{
+ mcview_search_status_msg_t vsm;
+
+ off_t search_start = 0;
+ off_t orig_search_start = view->search_start;
+ gboolean found = FALSE;
+
+ size_t match_len;
+
+ view->search_start = want_search_start;
+ /* for avoid infinite search loop we need to increase or decrease start offset of search */
+
+ if (view->search_start != 0)
+ {
+ if (!view->mode_flags.nroff)
+ search_start = view->search_start + (mcview_search_options.backwards ? -2 : 0);
+ else
+ {
+ if (mcview_search_options.backwards)
+ {
+ mcview_nroff_t *nroff;
+
+ nroff = mcview_nroff_seq_new_num (view, view->search_start);
+ if (mcview_nroff_seq_prev (nroff) != -1)
+ search_start =
+ -(mcview__get_nroff_real_len (view, nroff->index - 1, 2) +
+ nroff->char_length + 1);
+ else
+ search_start = -2;
+
+ mcview_nroff_seq_free (&nroff);
+ }
+ else
+ {
+ search_start = mcview__get_nroff_real_len (view, view->search_start + 1, 2);
+ }
+ search_start += view->search_start;
+ }
+ }
+
+ if (mcview_search_options.backwards && search_start < 0)
+ search_start = 0;
+
+ /* Compute the percent steps */
+ mcview_search_update_steps (view);
+
+ view->update_activate = search_start;
+
+ vsm.first = TRUE;
+ vsm.view = view;
+ vsm.offset = search_start;
+
+ status_msg_init (STATUS_MSG (&vsm), _("Search"), 1.0, simple_status_msg_init_cb,
+ mcview_search_status_update_cb, NULL);
+
+ do
+ {
+ off_t growbufsize;
+
+ if (view->growbuf_in_use)
+ growbufsize = mcview_growbuf_filesize (view);
+ else
+ growbufsize = view->search->original.str->len;
+
+ if (mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
+ {
+ mcview_search_show_result (view, match_len);
+ found = TRUE;
+ break;
+ }
+
+ /* Search error is here.
+ * MC_SEARCH_E_NOTFOUND: continue search
+ * others: stop
+ */
+ if (view->search->error != MC_SEARCH_E_NOTFOUND)
+ break;
+
+ search_start = growbufsize - view->search->original.str->len;
+ }
+ while (search_start > 0 && mcview_may_still_grow (view));
+
+ /* After mcview_may_still_grow (view) == FALSE we have remained last chunk. Search there. */
+ if (view->growbuf_in_use && !found && view->search->error == MC_SEARCH_E_NOTFOUND
+ && !mcview_search_options.backwards
+ && mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
+ {
+ mcview_search_show_result (view, match_len);
+ found = TRUE;
+ }
+
+ status_msg_deinit (STATUS_MSG (&vsm));
+
+ if (orig_search_start != 0 && (!found && view->search->error == MC_SEARCH_E_NOTFOUND)
+ && !mcview_search_options.backwards)
+ {
+ view->search_start = orig_search_start;
+ mcview_update (view);
+
+ if (query_dialog
+ (_("Search done"), _("Continue from beginning?"), D_NORMAL, 2, _("&Yes"),
+ _("&No")) != 0)
+ found = TRUE;
+ else
+ {
+ /* continue search from beginning */
+ view->update_activate = 0;
+
+ vsm.first = TRUE;
+ vsm.view = view;
+ vsm.offset = 0;
+
+ status_msg_init (STATUS_MSG (&vsm), _("Search"), 1.0, simple_status_msg_init_cb,
+ mcview_search_status_update_cb, NULL);
+
+ /* search from file begin up to initial search start position */
+ if (mcview_find (&vsm, 0, orig_search_start, &match_len))
+ {
+ mcview_search_show_result (view, match_len);
+ found = TRUE;
+ }
+
+ status_msg_deinit (STATUS_MSG (&vsm));
+ }
+ }
+
+ if (!found)
+ {
+ view->search_start = orig_search_start;
+ mcview_update (view);
+
+ if (view->search->error == MC_SEARCH_E_NOTFOUND)
+ query_dialog (_("Search"), _(STR_E_NOTFOUND), D_NORMAL, 1, _("&Dismiss"));
+ else if (view->search->error_str != NULL)
+ query_dialog (_("Search"), view->search->error_str, D_NORMAL, 1, _("&Dismiss"));
+ }
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */