From ffccd5b2b05243e7976db80f90f453dccfae9886 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 22:22:03 +0200 Subject: Adding upstream version 3:4.8.30. Signed-off-by: Daniel Baumann --- src/viewer/Makefile.am | 21 + src/viewer/Makefile.in | 793 ++++++++++++++++++++++++++++++++++ src/viewer/actions_cmd.c | 790 ++++++++++++++++++++++++++++++++++ src/viewer/ascii.c | 1051 ++++++++++++++++++++++++++++++++++++++++++++++ src/viewer/coord_cache.c | 395 +++++++++++++++++ src/viewer/datasource.c | 434 +++++++++++++++++++ src/viewer/dialogs.c | 263 ++++++++++++ src/viewer/display.c | 404 ++++++++++++++++++ src/viewer/growbuf.c | 297 +++++++++++++ src/viewer/hex.c | 484 +++++++++++++++++++++ src/viewer/internal.h | 472 +++++++++++++++++++++ src/viewer/lib.c | 437 +++++++++++++++++++ src/viewer/mcviewer.c | 469 +++++++++++++++++++++ src/viewer/mcviewer.h | 57 +++ src/viewer/move.c | 415 ++++++++++++++++++ src/viewer/nroff.c | 287 +++++++++++++ src/viewer/search.c | 491 ++++++++++++++++++++++ 17 files changed, 7560 insertions(+) create mode 100644 src/viewer/Makefile.am create mode 100644 src/viewer/Makefile.in create mode 100644 src/viewer/actions_cmd.c create mode 100644 src/viewer/ascii.c create mode 100644 src/viewer/coord_cache.c create mode 100644 src/viewer/datasource.c create mode 100644 src/viewer/dialogs.c create mode 100644 src/viewer/display.c create mode 100644 src/viewer/growbuf.c create mode 100644 src/viewer/hex.c create mode 100644 src/viewer/internal.h create mode 100644 src/viewer/lib.c create mode 100644 src/viewer/mcviewer.c create mode 100644 src/viewer/mcviewer.h create mode 100644 src/viewer/move.c create mode 100644 src/viewer/nroff.c create mode 100644 src/viewer/search.c (limited to 'src/viewer') 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 , 2004, 2005 + Slava Zanko , 2009, 2013 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +/* + 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 + +#include + +#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 = ¤t_panel->dir; + view->dir_idx = ¤t_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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 2009 + Rewritten almost from scratch by: + Egmont Koblinger , 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 . + + ------------------------------------------------------------------------------------------------ + + 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 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. "") 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 + +#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 + * .) + * + * 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, ¶graph_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, ¶graph_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, ¶graph_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, ¶graph_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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +/* + 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 + +#include /* memset() */ +#ifdef MC_ENABLE_DEBUGGING_CODE +#include /* 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 (¤t, 0, sizeof (current)); + mcview_ccache_add_entry (cache, ¤t); + } + + 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 (¤t, 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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009 + Ilia Maslakov , 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 . + */ + +/* + 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 + +#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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include + +#include +#include + +#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 *) ¤t_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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include +#include /* 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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009, 2014 + Ilia Maslakov , 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 . + */ + +#include +#include + +#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 , 2004, 2005 + Slava Zanko , 2009, 2013 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include + +#include +#include /* 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 /* CHAR_BIT */ +#include +#include +#include + +#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 , 2004, 2005 + Slava Zanko , 2009, 2013 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include + +#include /* memset() */ +#include + +#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 , 2004, 2005 + Slava Zanko , 2009, 2013 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include +#include + +#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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +/* + 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 + +#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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009 + Ilia Maslakov , 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 . + */ + +#include + +#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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include + +#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++; +} + +/* --------------------------------------------------------------------------------------------- */ -- cgit v1.2.3