summaryrefslogtreecommitdiffstats
path: root/src/editor
diff options
context:
space:
mode:
Diffstat (limited to 'src/editor')
-rw-r--r--src/editor/Makefile.am33
-rw-r--r--src/editor/Makefile.in801
-rw-r--r--src/editor/bookmark.c349
-rw-r--r--src/editor/edit-impl.h278
-rw-r--r--src/editor/edit.c4067
-rw-r--r--src/editor/edit.h84
-rw-r--r--src/editor/editbuffer.c900
-rw-r--r--src/editor/editbuffer.h117
-rw-r--r--src/editor/editcmd.c2108
-rw-r--r--src/editor/editcomplete.c483
-rw-r--r--src/editor/editcomplete.h21
-rw-r--r--src/editor/editdraw.c1122
-rw-r--r--src/editor/editmacros.c437
-rw-r--r--src/editor/editmacros.h24
-rw-r--r--src/editor/editmenu.c338
-rw-r--r--src/editor/editoptions.c241
-rw-r--r--src/editor/editsearch.c1042
-rw-r--r--src/editor/editsearch.h36
-rw-r--r--src/editor/editwidget.c1550
-rw-r--r--src/editor/editwidget.h173
-rw-r--r--src/editor/etags.c468
-rw-r--r--src/editor/etags.h26
-rw-r--r--src/editor/format.c538
-rw-r--r--src/editor/spell.c834
-rw-r--r--src/editor/spell.h36
-rw-r--r--src/editor/syntax.c1606
26 files changed, 17712 insertions, 0 deletions
diff --git a/src/editor/Makefile.am b/src/editor/Makefile.am
new file mode 100644
index 0000000..304cb35
--- /dev/null
+++ b/src/editor/Makefile.am
@@ -0,0 +1,33 @@
+EXTRA_DIST =
+
+if USE_INTERNAL_EDIT
+noinst_LTLIBRARIES = libedit.la
+else
+noinst_LTLIBRARIES =
+endif
+
+libedit_la_SOURCES = \
+ bookmark.c \
+ edit-impl.h \
+ edit.c edit.h \
+ editcomplete.c editcomplete.h \
+ editbuffer.c editbuffer.h \
+ editcmd.c \
+ editdraw.c \
+ editmacros.c editmacros.h \
+ editmenu.c \
+ editoptions.c \
+ editsearch.c editsearch.h \
+ editwidget.c editwidget.h \
+ etags.c etags.h \
+ format.c \
+ syntax.c
+
+if USE_ASPELL
+if HAVE_GMODULE
+libedit_la_SOURCES += \
+ spell.c spell.h
+endif
+endif
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
diff --git a/src/editor/Makefile.in b/src/editor/Makefile.in
new file mode 100644
index 0000000..b20d678
--- /dev/null
+++ b/src/editor/Makefile.in
@@ -0,0 +1,801 @@
+# 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@
+@HAVE_GMODULE_TRUE@@USE_ASPELL_TRUE@am__append_1 = \
+@HAVE_GMODULE_TRUE@@USE_ASPELL_TRUE@ spell.c spell.h
+
+subdir = src/editor
+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)
+libedit_la_LIBADD =
+am__libedit_la_SOURCES_DIST = bookmark.c edit-impl.h edit.c edit.h \
+ editcomplete.c editcomplete.h editbuffer.c editbuffer.h \
+ editcmd.c editdraw.c editmacros.c editmacros.h editmenu.c \
+ editoptions.c editsearch.c editsearch.h editwidget.c \
+ editwidget.h etags.c etags.h format.c syntax.c spell.c spell.h
+@HAVE_GMODULE_TRUE@@USE_ASPELL_TRUE@am__objects_1 = spell.lo
+am_libedit_la_OBJECTS = bookmark.lo edit.lo editcomplete.lo \
+ editbuffer.lo editcmd.lo editdraw.lo editmacros.lo editmenu.lo \
+ editoptions.lo editsearch.lo editwidget.lo etags.lo format.lo \
+ syntax.lo $(am__objects_1)
+libedit_la_OBJECTS = $(am_libedit_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 =
+@USE_INTERNAL_EDIT_TRUE@am_libedit_la_rpath =
+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)/bookmark.Plo ./$(DEPDIR)/edit.Plo \
+ ./$(DEPDIR)/editbuffer.Plo ./$(DEPDIR)/editcmd.Plo \
+ ./$(DEPDIR)/editcomplete.Plo ./$(DEPDIR)/editdraw.Plo \
+ ./$(DEPDIR)/editmacros.Plo ./$(DEPDIR)/editmenu.Plo \
+ ./$(DEPDIR)/editoptions.Plo ./$(DEPDIR)/editsearch.Plo \
+ ./$(DEPDIR)/editwidget.Plo ./$(DEPDIR)/etags.Plo \
+ ./$(DEPDIR)/format.Plo ./$(DEPDIR)/spell.Plo \
+ ./$(DEPDIR)/syntax.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 = $(libedit_la_SOURCES)
+DIST_SOURCES = $(am__libedit_la_SOURCES_DIST)
+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@
+EXTRA_DIST =
+@USE_INTERNAL_EDIT_FALSE@noinst_LTLIBRARIES =
+@USE_INTERNAL_EDIT_TRUE@noinst_LTLIBRARIES = libedit.la
+libedit_la_SOURCES = bookmark.c edit-impl.h edit.c edit.h \
+ editcomplete.c editcomplete.h editbuffer.c editbuffer.h \
+ editcmd.c editdraw.c editmacros.c editmacros.h editmenu.c \
+ editoptions.c editsearch.c editsearch.h editwidget.c \
+ editwidget.h etags.c etags.h format.c syntax.c $(am__append_1)
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+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/editor/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/editor/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}; \
+ }
+
+libedit.la: $(libedit_la_OBJECTS) $(libedit_la_DEPENDENCIES) $(EXTRA_libedit_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(am_libedit_la_rpath) $(libedit_la_OBJECTS) $(libedit_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bookmark.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edit.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editbuffer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editcmd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editcomplete.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editdraw.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editmacros.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editmenu.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editoptions.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editsearch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editwidget.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/etags.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/format.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spell.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/syntax.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)/bookmark.Plo
+ -rm -f ./$(DEPDIR)/edit.Plo
+ -rm -f ./$(DEPDIR)/editbuffer.Plo
+ -rm -f ./$(DEPDIR)/editcmd.Plo
+ -rm -f ./$(DEPDIR)/editcomplete.Plo
+ -rm -f ./$(DEPDIR)/editdraw.Plo
+ -rm -f ./$(DEPDIR)/editmacros.Plo
+ -rm -f ./$(DEPDIR)/editmenu.Plo
+ -rm -f ./$(DEPDIR)/editoptions.Plo
+ -rm -f ./$(DEPDIR)/editsearch.Plo
+ -rm -f ./$(DEPDIR)/editwidget.Plo
+ -rm -f ./$(DEPDIR)/etags.Plo
+ -rm -f ./$(DEPDIR)/format.Plo
+ -rm -f ./$(DEPDIR)/spell.Plo
+ -rm -f ./$(DEPDIR)/syntax.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)/bookmark.Plo
+ -rm -f ./$(DEPDIR)/edit.Plo
+ -rm -f ./$(DEPDIR)/editbuffer.Plo
+ -rm -f ./$(DEPDIR)/editcmd.Plo
+ -rm -f ./$(DEPDIR)/editcomplete.Plo
+ -rm -f ./$(DEPDIR)/editdraw.Plo
+ -rm -f ./$(DEPDIR)/editmacros.Plo
+ -rm -f ./$(DEPDIR)/editmenu.Plo
+ -rm -f ./$(DEPDIR)/editoptions.Plo
+ -rm -f ./$(DEPDIR)/editsearch.Plo
+ -rm -f ./$(DEPDIR)/editwidget.Plo
+ -rm -f ./$(DEPDIR)/etags.Plo
+ -rm -f ./$(DEPDIR)/format.Plo
+ -rm -f ./$(DEPDIR)/spell.Plo
+ -rm -f ./$(DEPDIR)/syntax.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/editor/bookmark.c b/src/editor/bookmark.c
new file mode 100644
index 0000000..d530660
--- /dev/null
+++ b/src/editor/bookmark.c
@@ -0,0 +1,349 @@
+/*
+ Editor book mark handling
+
+ Copyright (C) 2001-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: editor book mark handling
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/util.h" /* MAX_SAVED_BOOKMARKS */
+
+#include "editwidget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** note, if there is more than one bookmark on a line, then they are
+ appended after each other and the last one is always the one found
+ by book_mark_found() i.e. last in is the one seen */
+
+static edit_book_mark_t *
+double_marks (WEdit * edit, edit_book_mark_t * p)
+{
+ (void) edit;
+
+ if (p->next != NULL)
+ while (p->next->line == p->line)
+ p = p->next;
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns the first bookmark on or before this line */
+
+edit_book_mark_t *
+book_mark_find (WEdit * edit, long line)
+{
+ edit_book_mark_t *p;
+
+ if (edit->book_mark == NULL)
+ {
+ /* must have an imaginary top bookmark at line -1 to make things less complicated */
+ edit->book_mark = g_new0 (edit_book_mark_t, 1);
+ edit->book_mark->line = -1;
+ return edit->book_mark;
+ }
+
+ for (p = edit->book_mark; p != NULL; p = p->next)
+ {
+ if (p->line > line)
+ break; /* gone past it going downward */
+
+ if (p->next != NULL)
+ {
+ if (p->next->line > line)
+ {
+ edit->book_mark = p;
+ return double_marks (edit, p);
+ }
+ }
+ else
+ {
+ edit->book_mark = p;
+ return double_marks (edit, p);
+ }
+ }
+
+ for (p = edit->book_mark; p != NULL; p = p->prev)
+ {
+ if (p->next != NULL && p->next->line <= line)
+ break; /* gone past it going upward */
+
+ if (p->line <= line)
+ {
+ if (p->next != NULL)
+ {
+ if (p->next->line > line)
+ {
+ edit->book_mark = p;
+ return double_marks (edit, p);
+ }
+ }
+ else
+ {
+ edit->book_mark = p;
+ return double_marks (edit, p);
+ }
+ }
+ }
+
+ return NULL; /* can't get here */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Check if bookmark bookmark exists at this line of this color
+ *
+ * @param edit editor object
+ * @param line line where book mark is
+ * @param c color of book mark
+ * @return TRUE if bookmark exists at this line of color c, FALSE otherwise
+ */
+
+gboolean
+book_mark_query_color (WEdit * edit, long line, int c)
+{
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ for (p = book_mark_find (edit, line); p != NULL; p = p->prev)
+ {
+ if (p->line != line)
+ return FALSE;
+ if (p->c == c)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** insert a bookmark at this line */
+
+void
+book_mark_insert (WEdit * edit, long line, int c)
+{
+ edit_book_mark_t *p, *q;
+
+ p = book_mark_find (edit, line);
+#if 0
+ if (p->line == line)
+ {
+ /* already exists, so just change the color */
+ if (p->c != c)
+ {
+ p->c = c;
+ edit->force |= REDRAW_LINE;
+ }
+ return;
+ }
+#endif
+ /* create list entry */
+ q = g_new (edit_book_mark_t, 1);
+ q->line = line;
+ q->c = c;
+ q->next = p->next;
+ /* insert into list */
+ q->prev = p;
+ if (p->next != NULL)
+ p->next->prev = q;
+ p->next = q;
+
+ edit->force |= REDRAW_LINE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove a bookmark if there is one at this line matching this color - c of -1 clear all
+ *
+ * @param edit editor object
+ * @param line line where book mark is
+ * @param c color of book mark or -1 to clear all book marks on this line
+ * @return FALSE if not found, TRUE otherwise
+ */
+
+gboolean
+book_mark_clear (WEdit * edit, long line, int c)
+{
+ edit_book_mark_t *p, *q;
+ gboolean r = FALSE;
+
+ if (edit->book_mark == NULL)
+ return r;
+
+ for (p = book_mark_find (edit, line); p != NULL; p = q)
+ {
+ q = p->prev;
+ if (p->line == line && (p->c == c || c == -1))
+ {
+ r = TRUE;
+ edit->book_mark = p->prev;
+ p->prev->next = p->next;
+ if (p->next != NULL)
+ p->next->prev = p->prev;
+ g_free (p);
+ edit->force |= REDRAW_LINE;
+ break;
+ }
+ }
+ /* if there is only our dummy book mark left, clear it for speed */
+ if (edit->book_mark->line == -1 && edit->book_mark->next == NULL)
+ MC_PTR_FREE (edit->book_mark);
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** clear all bookmarks matching this color, if c is -1 clears all */
+
+void
+book_mark_flush (WEdit * edit, int c)
+{
+ edit_book_mark_t *p, *q;
+
+ if (edit->book_mark == NULL)
+ return;
+
+ while (edit->book_mark->prev != NULL)
+ edit->book_mark = edit->book_mark->prev;
+
+ for (q = edit->book_mark->next; q != NULL; q = p)
+ {
+ p = q->next;
+ if (q->c == c || c == -1)
+ {
+ q->prev->next = q->next;
+ if (p != NULL)
+ p->prev = q->prev;
+ g_free (q);
+ }
+ }
+ if (edit->book_mark->next == NULL)
+ MC_PTR_FREE (edit->book_mark);
+
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** shift down bookmarks after this line */
+
+void
+book_mark_inc (WEdit * edit, long line)
+{
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ p = book_mark_find (edit, line);
+ for (p = p->next; p != NULL; p = p->next)
+ p->line++;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** shift up bookmarks after this line */
+
+void
+book_mark_dec (WEdit * edit, long line)
+{
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ p = book_mark_find (edit, line);
+ for (p = p->next; p != NULL; p = p->next)
+ p->line--;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** prepare line positions of bookmarks to be saved to file */
+
+void
+book_mark_serialize (WEdit * edit, int color)
+{
+ if (edit->serialized_bookmarks != NULL)
+ g_array_set_size (edit->serialized_bookmarks, 0);
+
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ if (edit->serialized_bookmarks == NULL)
+ edit->serialized_bookmarks = g_array_sized_new (FALSE, FALSE, sizeof (size_t),
+ MAX_SAVED_BOOKMARKS);
+
+ for (p = book_mark_find (edit, 0); p != NULL; p = p->next)
+ if (p->c == color && p->line >= 0)
+ g_array_append_val (edit->serialized_bookmarks, p->line);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** restore bookmarks from saved line positions */
+
+void
+book_mark_restore (WEdit * edit, int color)
+{
+ if (edit->serialized_bookmarks != NULL)
+ {
+ size_t i;
+
+ for (i = 0; i < edit->serialized_bookmarks->len; i++)
+ book_mark_insert (edit, g_array_index (edit->serialized_bookmarks, size_t, i), color);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/edit-impl.h b/src/editor/edit-impl.h
new file mode 100644
index 0000000..3d00545
--- /dev/null
+++ b/src/editor/edit-impl.h
@@ -0,0 +1,278 @@
+/*
+ editor private API
+ */
+
+/** \file edit-impl.h
+ * \brief Header: editor low level data handling and cursor fundamentals
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#ifndef MC__EDIT_IMPL_H
+#define MC__EDIT_IMPL_H
+
+#include <stdio.h>
+
+#include "lib/search.h" /* mc_search_type_t */
+#include "lib/widget.h" /* cb_ret_t */
+#include "lib/vfs/vfs.h" /* vfs_path_t */
+
+#include "src/setup.h" /* option_tab_spacing */
+
+#include "edit.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define REDRAW_LINE (1 << 0)
+#define REDRAW_LINE_ABOVE (1 << 1)
+#define REDRAW_LINE_BELOW (1 << 2)
+#define REDRAW_AFTER_CURSOR (1 << 3)
+#define REDRAW_BEFORE_CURSOR (1 << 4)
+#define REDRAW_PAGE (1 << 5)
+#define REDRAW_IN_BOUNDS (1 << 6)
+#define REDRAW_CHAR_ONLY (1 << 7)
+#define REDRAW_COMPLETELY (1 << 8)
+
+#define EDIT_TEXT_HORIZONTAL_OFFSET 0
+#define EDIT_TEXT_VERTICAL_OFFSET 0
+
+#define EDIT_RIGHT_EXTREME 0
+#define EDIT_LEFT_EXTREME 0
+#define EDIT_TOP_EXTREME 0
+#define EDIT_BOTTOM_EXTREME 0
+
+/* Initial size of the undo stack, in bytes */
+#define START_STACK_SIZE 32
+
+/* Some codes that may be pushed onto or returned from the undo stack */
+#define CURS_LEFT 601
+#define CURS_RIGHT 602
+#define DELCHAR 603
+#define BACKSPACE 604
+#define STACK_BOTTOM 605
+#define CURS_LEFT_LOTS 606
+#define CURS_RIGHT_LOTS 607
+#define COLUMN_ON 608
+#define COLUMN_OFF 609
+#define DELCHAR_BR 610
+#define BACKSPACE_BR 611
+#define MARK_1 1000
+#define MARK_2 500000000
+#define MARK_CURS 1000000000
+#define KEY_PRESS 1500000000
+
+/* Tabs spaces: (sofar only HALF_TAB_SIZE is used: */
+#define TAB_SIZE option_tab_spacing
+#define HALF_TAB_SIZE ((int) option_tab_spacing / 2)
+
+/* max count stack files */
+#define MAX_HISTORY_MOVETO 50
+#define LINE_STATE_WIDTH 8
+
+#define LB_NAMES (LB_MAC + 1)
+
+#define get_sys_error(s) (s)
+
+#define edit_error_dialog(h,s) query_dialog (h, s, D_ERROR, 1, _("&Dismiss"))
+#define edit_query_dialog(h,s) query_dialog (h, s, D_NORMAL, 1, _("&Dismiss"))
+#define edit_query_dialog2(h,t,a,b) query_dialog (h, t, D_NORMAL, 2, a, b)
+#define edit_query_dialog3(h,t,a,b,c) query_dialog (h, t, D_NORMAL, 3, a, b, c)
+
+/*** enums ***************************************************************************************/
+
+/* line breaks */
+typedef enum
+{
+ LB_ASIS = 0,
+ LB_UNIX,
+ LB_WIN,
+ LB_MAC
+} LineBreaks;
+
+typedef enum
+{
+ EDIT_QUICK_SAVE = 0,
+ EDIT_SAFE_SAVE,
+ EDIT_DO_BACKUP
+} edit_save_mode_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* search/replace options */
+typedef struct edit_search_options_t
+{
+ mc_search_type_t type;
+ gboolean case_sens;
+ gboolean backwards;
+ gboolean only_in_selection;
+ gboolean whole_words;
+ gboolean all_codepages;
+} edit_search_options_t;
+
+typedef struct edit_stack_type
+{
+ long line;
+ vfs_path_t *filename_vpath;
+} edit_stack_type;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const char VERTICAL_MAGIC[5];
+/* if enable_show_tabs_tws == TRUE then use visible_tab visible_tws */
+extern gboolean enable_show_tabs_tws;
+
+extern edit_search_options_t edit_search_options;
+
+extern unsigned int edit_stack_iterator;
+extern edit_stack_type edit_history_moveto[MAX_HISTORY_MOVETO];
+
+extern int max_undo;
+extern gboolean auto_syntax;
+
+extern gboolean search_create_bookmark;
+
+extern char *edit_window_state_char;
+extern char *edit_window_close_char;
+
+/*** declarations of public functions ************************************************************/
+
+gboolean edit_add_window (WDialog * h, const WRect * r, const vfs_path_t * f, long fline);
+WEdit *edit_find_editor (const WDialog * h);
+gboolean edit_widget_is_editor (const Widget * w);
+gboolean edit_drop_hotkey_menu (WDialog * h, int key);
+void edit_menu_cmd (WDialog * h);
+void user_menu (WEdit * edit, const char *menu_file, int selected_entry);
+void edit_init_menu (WMenuBar * menubar);
+void edit_save_mode_cmd (void);
+off_t edit_move_forward3 (const WEdit * edit, off_t current, long cols, off_t upto);
+void edit_scroll_screen_over_cursor (WEdit * edit);
+void edit_render_keypress (WEdit * edit);
+void edit_scroll_upward (WEdit * edit, long i);
+void edit_scroll_downward (WEdit * edit, long i);
+void edit_scroll_right (WEdit * edit, long i);
+void edit_scroll_left (WEdit * edit, long i);
+void edit_move_up (WEdit * edit, long i, gboolean do_scroll);
+void edit_move_down (WEdit * edit, long i, gboolean do_scroll);
+void edit_move_to_prev_col (WEdit * edit, off_t p);
+long edit_get_col (const WEdit * edit);
+void edit_update_curs_row (WEdit * edit);
+void edit_update_curs_col (WEdit * edit);
+void edit_find_bracket (WEdit * edit);
+gboolean edit_reload_line (WEdit * edit, const vfs_path_t * filename_vpath, long line);
+void edit_set_codeset (WEdit * edit);
+
+void edit_block_copy_cmd (WEdit * edit);
+void edit_block_move_cmd (WEdit * edit);
+int edit_block_delete_cmd (WEdit * edit);
+void edit_delete_line (WEdit * edit);
+
+int edit_delete (WEdit * edit, gboolean byte_delete);
+int edit_backspace (WEdit * edit, gboolean byte_delete);
+void edit_insert (WEdit * edit, int c);
+void edit_insert_over (WEdit * edit);
+void edit_cursor_move (WEdit * edit, off_t increment);
+void edit_push_undo_action (WEdit * edit, long c);
+void edit_push_redo_action (WEdit * edit, long c);
+void edit_push_key_press (WEdit * edit);
+void edit_insert_ahead (WEdit * edit, int c);
+off_t edit_write_stream (WEdit * edit, FILE * f);
+char *edit_get_write_filter (const vfs_path_t * write_name_vpath,
+ const vfs_path_t * filename_vpath);
+gboolean edit_save_confirm_cmd (WEdit * edit);
+gboolean edit_save_as_cmd (WEdit * edit);
+WEdit *edit_init (WEdit * edit, const WRect * r, const vfs_path_t * filename_vpath, long line);
+gboolean edit_clean (WEdit * edit);
+gboolean edit_ok_to_exit (WEdit * edit);
+gboolean edit_load_cmd (WDialog * h);
+gboolean edit_load_file_from_filename (WDialog * h, const vfs_path_t * vpath, long line);
+gboolean edit_load_file_from_history (WDialog * h);
+gboolean edit_load_syntax_file (WDialog * h);
+gboolean edit_load_menu_file (WDialog * h);
+gboolean edit_close_cmd (WEdit * edit);
+void edit_mark_cmd (WEdit * edit, gboolean unmark);
+void edit_mark_current_word_cmd (WEdit * edit);
+void edit_mark_current_line_cmd (WEdit * edit);
+void edit_set_markers (WEdit * edit, off_t m1, off_t m2, long c1, long c2);
+void edit_push_markers (WEdit * edit);
+
+gboolean edit_save_block (WEdit * edit, const char *filename, off_t start, off_t finish);
+gboolean edit_save_block_cmd (WEdit * edit);
+gboolean edit_insert_file_cmd (WEdit * edit);
+
+off_t edit_insert_file (WEdit * edit, const vfs_path_t * filename_vpath);
+gboolean edit_load_back_cmd (WEdit * edit);
+gboolean edit_load_forward_cmd (WEdit * edit);
+void edit_block_process_cmd (WEdit * edit, int macro_number);
+void edit_refresh_cmd (void);
+void edit_syntax_onoff_cmd (WDialog * h);
+void edit_show_tabs_tws_cmd (WDialog * h);
+void edit_show_margin_cmd (WDialog * h);
+void edit_show_numbers_cmd (WDialog * h);
+void edit_date_cmd (WEdit * edit);
+void edit_goto_cmd (WEdit * edit);
+gboolean eval_marks (WEdit * edit, off_t * start_mark, off_t * end_mark);
+void edit_status (WEdit * edit, gboolean active);
+void edit_execute_key_command (WEdit * edit, long command, int char_for_insertion);
+void edit_update_screen (WEdit * edit);
+void edit_save_size (WEdit * edit);
+gboolean edit_handle_move_resize (WEdit * edit, long command);
+void edit_toggle_fullscreen (WEdit * edit);
+void edit_move_to_line (WEdit * e, long line);
+void edit_move_display (WEdit * e, long line);
+void edit_word_wrap (WEdit * edit);
+int edit_sort_cmd (WEdit * edit);
+int edit_ext_cmd (WEdit * edit);
+
+gboolean edit_copy_to_X_buf_cmd (WEdit * edit);
+gboolean edit_cut_to_X_buf_cmd (WEdit * edit);
+gboolean edit_paste_from_X_buf_cmd (WEdit * edit);
+
+void edit_select_codepage_cmd (WEdit * edit);
+void edit_insert_literal_cmd (WEdit * edit);
+
+void edit_paste_from_history (WEdit * edit);
+
+void edit_set_filename (WEdit * edit, const vfs_path_t * name_vpath);
+
+void edit_load_syntax (WEdit * edit, GPtrArray * pnames, const char *type);
+void edit_free_syntax_rules (WEdit * edit);
+int edit_get_syntax_color (WEdit * edit, off_t byte_index);
+void edit_syntax_dialog (WEdit * edit);
+
+void book_mark_insert (WEdit * edit, long line, int c);
+gboolean book_mark_query_color (WEdit * edit, long line, int c);
+struct edit_book_mark_t *book_mark_find (WEdit * edit, long line);
+gboolean book_mark_clear (WEdit * edit, long line, int c);
+void book_mark_flush (WEdit * edit, int c);
+void book_mark_inc (WEdit * edit, long line);
+void book_mark_dec (WEdit * edit, long line);
+void book_mark_serialize (WEdit * edit, int color);
+void book_mark_restore (WEdit * edit, int color);
+
+gboolean edit_line_is_blank (WEdit * edit, long line);
+gboolean is_break_char (char c);
+void edit_options_dialog (WDialog * h);
+void edit_mail_dialog (WEdit * edit);
+void format_paragraph (WEdit * edit, gboolean force);
+
+/* either command or char_for_insertion must be passed as -1 */
+void edit_execute_cmd (WEdit * edit, long command, int char_for_insertion);
+
+int editcmd_dialog_raw_key_query (const char *heading, const char *query, gboolean cancel);
+
+/*** inline functions ****************************************************************************/
+
+/**
+ * Load a new file into the editor. If it fails, preserve the old file.
+ * To do it, allocate a new widget, initialize it and, if the new file
+ * was loaded, copy the data to the old widget.
+ *
+ * @return TRUE on success, FALSE on failure.
+ */
+static inline gboolean
+edit_reload (WEdit * edit, const vfs_path_t * filename_vpath)
+{
+ return edit_reload_line (edit, filename_vpath, 0);
+}
+
+#endif /* MC__EDIT_IMPL_H */
diff --git a/src/editor/edit.c b/src/editor/edit.c
new file mode 100644
index 0000000..dc3b322
--- /dev/null
+++ b/src/editor/edit.c
@@ -0,0 +1,4067 @@
+/*
+ Editor low level data handling and cursor fundamentals.
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer 1996, 1997
+ Ilia Maslakov <il.smind@gmail.com> 2009, 2010, 2011
+ Andrew Borodin <aborodin@vmail.ru> 2012-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: editor low level data handling and cursor fundamentals
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <stdint.h> /* UINTMAX_MAX */
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/color.h"
+#include "lib/tty/tty.h" /* attrset() */
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/skin.h" /* EDITOR_NORMAL_COLOR */
+#include "lib/fileloc.h" /* EDIT_HOME_BLOCK_FILE */
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h" /* utf string functions */
+#include "lib/util.h" /* load_file_position(), save_file_position() */
+#include "lib/timefmt.h" /* time formatting */
+#include "lib/lock.h"
+#include "lib/widget.h"
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* get_codepage_id */
+#endif
+
+#include "src/usermenu.h" /* user_menu_cmd() */
+
+#include "src/keymap.h"
+
+#include "edit-impl.h"
+#include "editwidget.h"
+#include "editsearch.h"
+#include "editcomplete.h" /* edit_complete_word_cmd() */
+#include "editmacros.h"
+#include "etags.h" /* edit_get_match_keyword_cmd() */
+#ifdef HAVE_ASPELL
+#include "spell.h"
+#endif
+
+/*** global variables ****************************************************************************/
+
+edit_options_t edit_options = {
+ .word_wrap_line_length = DEFAULT_WRAP_LINE_LENGTH,
+ .typewriter_wrap = FALSE,
+ .auto_para_formatting = FALSE,
+ .fill_tabs_with_spaces = FALSE,
+ .return_does_auto_indent = TRUE,
+ .backspace_through_tabs = FALSE,
+ .fake_half_tabs = TRUE,
+ .persistent_selections = TRUE,
+ .drop_selection_on_copy = TRUE,
+ .cursor_beyond_eol = FALSE,
+ .cursor_after_inserted_block = FALSE,
+ .state_full_filename = FALSE,
+ .line_state = FALSE,
+ .line_state_width = 0,
+ .save_mode = EDIT_QUICK_SAVE,
+ .confirm_save = TRUE,
+ .save_position = TRUE,
+ .syntax_highlighting = TRUE,
+ .group_undo = FALSE,
+ .backup_ext = NULL,
+ .filesize_threshold = NULL,
+ .stop_format_chars = NULL,
+ .visible_tabs = TRUE,
+ .visible_tws = TRUE,
+ .show_right_margin = FALSE,
+ .simple_statusbar = FALSE,
+ .check_nl_at_eof = FALSE
+};
+
+int max_undo = 32768;
+
+gboolean enable_show_tabs_tws = TRUE;
+
+unsigned int edit_stack_iterator = 0;
+edit_stack_type edit_history_moveto[MAX_HISTORY_MOVETO];
+/* magic sequence for say than block is vertical */
+const char VERTICAL_MAGIC[] = { '\1', '\1', '\1', '\1', '\n' };
+
+/*** file scope macro definitions ****************************************************************/
+
+#define TEMP_BUF_LEN 1024
+
+#define space_width 1
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* detecting an error on save is easy: just check if every byte has been written. */
+/* detecting an error on read, is not so easy 'cos there is not way to tell
+ whether you read everything or not. */
+/* FIXME: add proper 'triple_pipe_open' to read, write and check errors. */
+static const struct edit_filters
+{
+ const char *read, *write, *extension;
+} all_filters[] =
+{
+ /* *INDENT-OFF* */
+ { "xz -cd %s 2>&1", "xz > %s", ".xz"},
+ { "zstd -cd %s 2>&1", "zstd > %s", ".zst"},
+ { "lz4 -cd %s 2>&1", "lz4 > %s", ".lz4" },
+ { "lzip -cd %s 2>&1", "lzip > %s", ".lz"},
+ { "lzma -cd %s 2>&1", "lzma > %s", ".lzma" },
+ { "bzip2 -cd %s 2>&1", "bzip2 > %s", ".bz2" },
+ { "gzip -cd %s 2>&1", "gzip > %s", ".gz" },
+ { "gzip -cd %s 2>&1", "gzip > %s", ".Z" }
+ /* *INDENT-ON* */
+};
+
+static const off_t filesize_default_threshold = 64 * 1024 * 1024; /* 64 MB */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+edit_load_status_update_cb (status_msg_t * sm)
+{
+ simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
+ edit_buffer_read_file_status_msg_t *rsm = (edit_buffer_read_file_status_msg_t *) sm;
+ Widget *wd = WIDGET (sm->dlg);
+
+ if (verbose)
+ label_set_textv (ssm->label, _("Loading: %3d%%"),
+ edit_buffer_calc_percent (rsm->buf, rsm->loaded));
+ else
+ label_set_text (ssm->label, _("Loading..."));
+
+ if (rsm->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);
+ rsm->first = FALSE;
+ }
+
+ return status_msg_common_update (sm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load file OR text into buffers. Set cursor to the beginning of file.
+ *
+ * @return FALSE on error.
+ */
+
+static gboolean
+edit_load_file_fast (edit_buffer_t * buf, const vfs_path_t * filename_vpath)
+{
+ int file;
+ gboolean ret;
+ edit_buffer_read_file_status_msg_t rsm;
+ gboolean aborted;
+
+ file = mc_open (filename_vpath, O_RDONLY | O_BINARY);
+ if (file < 0)
+ {
+ gchar *errmsg;
+
+ errmsg =
+ g_strdup_printf (_("Cannot open %s for reading"), vfs_path_as_str (filename_vpath));
+ edit_error_dialog (_("Error"), errmsg);
+ g_free (errmsg);
+ return FALSE;
+ }
+
+ rsm.first = TRUE;
+ rsm.buf = buf;
+ rsm.loaded = 0;
+
+ status_msg_init (STATUS_MSG (&rsm), _("Load file"), 1.0, simple_status_msg_init_cb,
+ edit_load_status_update_cb, NULL);
+
+ ret = (edit_buffer_read_file (buf, file, buf->size, &rsm, &aborted) == buf->size);
+
+ status_msg_deinit (STATUS_MSG (&rsm));
+
+ if (!ret && !aborted)
+ {
+ gchar *errmsg;
+
+ errmsg = g_strdup_printf (_("Error reading %s"), vfs_path_as_str (filename_vpath));
+ edit_error_dialog (_("Error"), errmsg);
+ g_free (errmsg);
+ }
+
+ mc_close (file);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return index of the filter or -1 is there is no appropriate filter */
+
+static int
+edit_find_filter (const vfs_path_t * filename_vpath)
+{
+ if (filename_vpath != NULL)
+ {
+ const char *s;
+ size_t i;
+
+ s = vfs_path_as_str (filename_vpath);
+
+ for (i = 0; i < G_N_ELEMENTS (all_filters); i++)
+ if (g_str_has_suffix (s, all_filters[i].extension))
+ return i;
+ }
+
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_get_filter (const vfs_path_t * filename_vpath)
+{
+ int i;
+ char *p, *quoted_name;
+
+ i = edit_find_filter (filename_vpath);
+ if (i < 0)
+ return NULL;
+
+ quoted_name = name_quote (vfs_path_as_str (filename_vpath), FALSE);
+ p = g_strdup_printf (all_filters[i].read, quoted_name);
+ g_free (quoted_name);
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+edit_insert_stream (WEdit * edit, FILE * f)
+{
+ int c;
+ off_t i;
+
+ for (i = 0; (c = fgetc (f)) >= 0; i++)
+ edit_insert (edit, c);
+
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open file and create it if necessary.
+ *
+ * @param edit editor object
+ * @param filename_vpath file name
+ * @param st buffer for store stat info
+ * @return TRUE for success, FALSE for error.
+ */
+
+static gboolean
+check_file_access (WEdit * edit, const vfs_path_t * filename_vpath, struct stat *st)
+{
+ static uintmax_t threshold = UINTMAX_MAX;
+ int file;
+ gchar *errmsg = NULL;
+ gboolean ret = TRUE;
+
+ /* Try opening an existing file */
+ file = mc_open (filename_vpath, O_NONBLOCK | O_RDONLY | O_BINARY, 0666);
+ if (file < 0)
+ {
+ /*
+ * Try creating the file. O_EXCL prevents following broken links
+ * and opening existing files.
+ */
+ file = mc_open (filename_vpath, O_NONBLOCK | O_RDONLY | O_BINARY | O_CREAT | O_EXCL, 0666);
+ if (file < 0)
+ {
+ errmsg =
+ g_strdup_printf (_("Cannot open %s for reading"), vfs_path_as_str (filename_vpath));
+ goto cleanup;
+ }
+
+ /* New file, delete it if it's not modified or saved */
+ edit->delete_file = 1;
+ }
+
+ /* Check what we have opened */
+ if (mc_fstat (file, st) < 0)
+ {
+ errmsg =
+ g_strdup_printf (_("Cannot get size/permissions for %s"),
+ vfs_path_as_str (filename_vpath));
+ goto cleanup;
+ }
+
+ /* We want to open regular files only */
+ if (!S_ISREG (st->st_mode))
+ {
+ errmsg =
+ g_strdup_printf (_("\"%s\" is not a regular file"), vfs_path_as_str (filename_vpath));
+ goto cleanup;
+ }
+
+ /* get file size threshold for alarm */
+ if (threshold == UINTMAX_MAX)
+ {
+ gboolean err = FALSE;
+
+ threshold = parse_integer (edit_options.filesize_threshold, &err);
+ if (err)
+ threshold = filesize_default_threshold;
+ }
+
+ /*
+ * Don't delete non-empty files.
+ * O_EXCL should prevent it, but let's be on the safe side.
+ */
+ if (st->st_size > 0)
+ edit->delete_file = 0;
+
+ if ((uintmax_t) st->st_size > threshold)
+ {
+ int act;
+
+ errmsg = g_strdup_printf (_("File \"%s\" is too large.\nOpen it anyway?"),
+ vfs_path_as_str (filename_vpath));
+ act = edit_query_dialog2 (_("Warning"), errmsg, _("&Yes"), _("&No"));
+ MC_PTR_FREE (errmsg);
+
+ if (act != 0)
+ ret = FALSE;
+ }
+
+ cleanup:
+ (void) mc_close (file);
+
+ if (errmsg != NULL)
+ {
+ edit_error_dialog (_("Error"), errmsg);
+ g_free (errmsg);
+ ret = FALSE;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Open the file and load it into the buffers, either directly or using
+ * a filter. Return TRUE on success, FALSE on error.
+ *
+ * Fast loading (edit_load_file_fast) is used when the file size is
+ * known. In this case the data is read into the buffers by blocks.
+ * If the file size is not known, the data is loaded byte by byte in
+ * edit_insert_file.
+ *
+ * @param edit editor object
+ * @return TRUE if file was successfully opened and loaded to buffers, FALSE otherwise
+ */
+static gboolean
+edit_load_file (WEdit * edit)
+{
+ gboolean fast_load = TRUE;
+
+ /* Cannot do fast load if a filter is used */
+ if (edit_find_filter (edit->filename_vpath) >= 0)
+ fast_load = FALSE;
+
+ /*
+ * FIXME: line end translation should disable fast loading as well
+ * Consider doing fseek() to the end and ftell() for the real size.
+ */
+ if (edit->filename_vpath != NULL)
+ {
+ /*
+ * VFS may report file size incorrectly, and slow load is not a big
+ * deal considering overhead in VFS.
+ */
+ if (!vfs_file_is_local (edit->filename_vpath))
+ fast_load = FALSE;
+
+ /* If we are dealing with a real file, check that it exists */
+ if (!check_file_access (edit, edit->filename_vpath, &edit->stat1))
+ {
+ edit_clean (edit);
+ return FALSE;
+ }
+ }
+ else
+ {
+ /* nothing to load */
+ fast_load = FALSE;
+ }
+
+ if (fast_load)
+ {
+ edit_buffer_init (&edit->buffer, edit->stat1.st_size);
+
+ if (!edit_load_file_fast (&edit->buffer, edit->filename_vpath))
+ {
+ edit_clean (edit);
+ return FALSE;
+ }
+ }
+ else
+ {
+ edit_buffer_init (&edit->buffer, 0);
+
+ if (edit->filename_vpath != NULL
+ && *(vfs_path_get_by_index (edit->filename_vpath, 0)->path) != '\0')
+ {
+ edit->undo_stack_disable = 1;
+ if (edit_insert_file (edit, edit->filename_vpath) < 0)
+ {
+ edit_clean (edit);
+ return FALSE;
+ }
+ edit->undo_stack_disable = 0;
+ }
+ }
+ edit->lb = LB_ASIS;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Restore saved cursor position and/or bookmarks in the file
+ *
+ * @param edit editor object
+ * @param load_position If TRUE, load bookmarks and cursor position and apply them.
+ * If FALSE, load bookmarks only.
+ */
+
+static void
+edit_load_position (WEdit * edit, gboolean load_position)
+{
+ long line, column;
+ off_t offset;
+
+ if (edit->filename_vpath == NULL
+ || *(vfs_path_get_by_index (edit->filename_vpath, 0)->path) == '\0')
+ return;
+
+ load_file_position (edit->filename_vpath, &line, &column, &offset, &edit->serialized_bookmarks);
+ /* apply bookmarks in any case */
+ book_mark_restore (edit, BOOK_MARK_COLOR);
+
+ if (!load_position)
+ return;
+
+ if (line > 0)
+ {
+ edit_move_to_line (edit, line - 1);
+ edit->prev_col = column;
+ }
+ else if (offset > 0)
+ {
+ edit_cursor_move (edit, offset);
+ line = edit->buffer.curs_line;
+ edit->search_start = edit->buffer.curs1;
+ }
+
+ edit_move_to_prev_col (edit, edit_buffer_get_current_bol (&edit->buffer));
+ edit_move_display (edit, line - (WIDGET (edit)->rect.lines / 2));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Save cursor position in the file */
+
+static void
+edit_save_position (WEdit * edit)
+{
+ if (edit->filename_vpath == NULL
+ || *(vfs_path_get_by_index (edit->filename_vpath, 0)->path) == '\0')
+ return;
+
+ book_mark_serialize (edit, BOOK_MARK_COLOR);
+ save_file_position (edit->filename_vpath, edit->buffer.curs_line + 1, edit->curs_col,
+ edit->buffer.curs1, edit->serialized_bookmarks);
+ edit->serialized_bookmarks = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Clean the WEdit stricture except the widget part */
+
+static void
+edit_purge_widget (WEdit * edit)
+{
+ size_t len = sizeof (WEdit) - sizeof (Widget);
+ char *start = (char *) edit + sizeof (Widget);
+ memset (start, 0, len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ TODO: if the user undos until the stack bottom, and the stack has not wrapped,
+ then the file should be as it was when he loaded up. Then set edit->modified to 0.
+ */
+
+static long
+edit_pop_undo_action (WEdit * edit)
+{
+ long c;
+ unsigned long sp = edit->undo_stack_pointer;
+
+ if (sp == edit->undo_stack_bottom)
+ return STACK_BOTTOM;
+
+ sp = (sp - 1) & edit->undo_stack_size_mask;
+ c = edit->undo_stack[sp];
+ if (c >= 0)
+ {
+ /* edit->undo_stack[sp] = '@'; */
+ edit->undo_stack_pointer = (edit->undo_stack_pointer - 1) & edit->undo_stack_size_mask;
+ return c;
+ }
+
+ if (sp == edit->undo_stack_bottom)
+ return STACK_BOTTOM;
+
+ c = edit->undo_stack[(sp - 1) & edit->undo_stack_size_mask];
+ if (edit->undo_stack[sp] == -2)
+ {
+ /* edit->undo_stack[sp] = '@'; */
+ edit->undo_stack_pointer = sp;
+ }
+ else
+ edit->undo_stack[sp]++;
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+edit_pop_redo_action (WEdit * edit)
+{
+ long c;
+ unsigned long sp = edit->redo_stack_pointer;
+
+ if (sp == edit->redo_stack_bottom)
+ return STACK_BOTTOM;
+
+ sp = (sp - 1) & edit->redo_stack_size_mask;
+ c = edit->redo_stack[sp];
+ if (c >= 0)
+ {
+ edit->redo_stack_pointer = (edit->redo_stack_pointer - 1) & edit->redo_stack_size_mask;
+ return c;
+ }
+
+ if (sp == edit->redo_stack_bottom)
+ return STACK_BOTTOM;
+
+ c = edit->redo_stack[(sp - 1) & edit->redo_stack_size_mask];
+ if (edit->redo_stack[sp] == -2)
+ edit->redo_stack_pointer = sp;
+ else
+ edit->redo_stack[sp]++;
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+get_prev_undo_action (WEdit * edit)
+{
+ long c;
+ unsigned long sp = edit->undo_stack_pointer;
+
+ if (sp == edit->undo_stack_bottom)
+ return STACK_BOTTOM;
+
+ sp = (sp - 1) & edit->undo_stack_size_mask;
+ c = edit->undo_stack[sp];
+ if (c >= 0)
+ return c;
+
+ if (sp == edit->undo_stack_bottom)
+ return STACK_BOTTOM;
+
+ c = edit->undo_stack[(sp - 1) & edit->undo_stack_size_mask];
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** is called whenever a modification is made by one of the four routines below */
+
+static void
+edit_modification (WEdit * edit)
+{
+ edit->caches_valid = FALSE;
+
+ /* raise lock when file modified */
+ if (!edit->modified && !edit->delete_file)
+ edit->locked = lock_file (edit->filename_vpath);
+ edit->modified = 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* high level cursor movement commands */
+/* --------------------------------------------------------------------------------------------- */
+/** check whether cursor is in indent part of line
+ *
+ * @param edit editor object
+ *
+ * @return TRUE if cursor is in indent, FALSE otherwise
+ */
+
+static gboolean
+is_in_indent (const edit_buffer_t * buf)
+{
+ off_t p;
+
+ for (p = edit_buffer_get_current_bol (buf); p < buf->curs1; p++)
+ if (strchr (" \t", edit_buffer_get_byte (buf, p)) == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** check whether line in editor is blank or not
+ *
+ * @param edit editor object
+ * @param offset position in file
+ *
+ * @return TRUE if line in blank, FALSE otherwise
+ */
+
+static gboolean
+is_blank (const edit_buffer_t * buf, off_t offset)
+{
+ off_t s, f;
+
+ s = edit_buffer_get_bol (buf, offset);
+ f = edit_buffer_get_eol (buf, offset) - 1;
+ while (s <= f)
+ {
+ int c;
+
+ c = edit_buffer_get_byte (buf, s++);
+ if (!isspace (c))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns the offset of line i */
+
+static off_t
+edit_find_line (WEdit * edit, long line)
+{
+ long i, j = 0;
+ long m = 2000000000; /* what is the magic number? */
+
+ if (!edit->caches_valid)
+ {
+ memset (edit->line_numbers, 0, sizeof (edit->line_numbers));
+ memset (edit->line_offsets, 0, sizeof (edit->line_offsets));
+ /* three offsets that we *know* are line 0 at 0 and these two: */
+ edit->line_numbers[1] = edit->buffer.curs_line;
+ edit->line_offsets[1] = edit_buffer_get_current_bol (&edit->buffer);
+ edit->line_numbers[2] = edit->buffer.lines;
+ edit->line_offsets[2] = edit_buffer_get_bol (&edit->buffer, edit->buffer.size);
+ edit->caches_valid = TRUE;
+ }
+ if (line >= edit->buffer.lines)
+ return edit->line_offsets[2];
+ if (line <= 0)
+ return 0;
+ /* find the closest known point */
+ for (i = 0; i < N_LINE_CACHES; i++)
+ {
+ long n;
+
+ n = labs (edit->line_numbers[i] - line);
+ if (n < m)
+ {
+ m = n;
+ j = i;
+ }
+ }
+ if (m == 0)
+ return edit->line_offsets[j]; /* know the offset exactly */
+ if (m == 1 && j >= 3)
+ i = j; /* one line different - caller might be looping, so stay in this cache */
+ else
+ i = 3 + (rand () % (N_LINE_CACHES - 3));
+ if (line > edit->line_numbers[j])
+ edit->line_offsets[i] =
+ edit_buffer_get_forward_offset (&edit->buffer, edit->line_offsets[j],
+ line - edit->line_numbers[j], 0);
+ else
+ edit->line_offsets[i] =
+ edit_buffer_get_backward_offset (&edit->buffer, edit->line_offsets[j],
+ edit->line_numbers[j] - line);
+ edit->line_numbers[i] = line;
+ return edit->line_offsets[i];
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** moves up until a blank line is reached, or until just
+ before a non-blank line is reached */
+
+static void
+edit_move_up_paragraph (WEdit * edit, gboolean do_scroll)
+{
+ long i = 0;
+
+ if (edit->buffer.curs_line > 1)
+ {
+ if (!edit_line_is_blank (edit, edit->buffer.curs_line))
+ {
+ for (i = edit->buffer.curs_line - 1; i != 0; i--)
+ if (edit_line_is_blank (edit, i))
+ break;
+ }
+ else if (edit_line_is_blank (edit, edit->buffer.curs_line - 1))
+ {
+ for (i = edit->buffer.curs_line - 1; i != 0; i--)
+ if (!edit_line_is_blank (edit, i))
+ {
+ i++;
+ break;
+ }
+ }
+ else
+ {
+ for (i = edit->buffer.curs_line - 1; i != 0; i--)
+ if (edit_line_is_blank (edit, i))
+ break;
+ }
+ }
+
+ edit_move_up (edit, edit->buffer.curs_line - i, do_scroll);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** moves down until a blank line is reached, or until just
+ before a non-blank line is reached */
+
+static void
+edit_move_down_paragraph (WEdit * edit, gboolean do_scroll)
+{
+ long i;
+
+ if (edit->buffer.curs_line >= edit->buffer.lines - 1)
+ i = edit->buffer.lines;
+ else if (!edit_line_is_blank (edit, edit->buffer.curs_line))
+ {
+ for (i = edit->buffer.curs_line + 1; i != 0; i++)
+ if (edit_line_is_blank (edit, i) || i >= edit->buffer.lines)
+ break;
+ }
+ else if (edit_line_is_blank (edit, edit->buffer.curs_line + 1))
+ {
+ for (i = edit->buffer.curs_line + 1; i != 0; i++)
+ if (!edit_line_is_blank (edit, i) || i > edit->buffer.lines)
+ {
+ i--;
+ break;
+ }
+ }
+ else
+ {
+ for (i = edit->buffer.curs_line + 1; i != 0; i++)
+ if (edit_line_is_blank (edit, i) || i >= edit->buffer.lines)
+ break;
+ }
+ edit_move_down (edit, i - edit->buffer.curs_line, do_scroll);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_begin_page (WEdit * edit)
+{
+ edit_update_curs_row (edit);
+ edit_move_up (edit, edit->curs_row, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_end_page (WEdit * edit)
+{
+ edit_update_curs_row (edit);
+ edit_move_down (edit, WIDGET (edit)->rect.lines - edit->curs_row - 1, FALSE);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/** goto beginning of text */
+
+static void
+edit_move_to_top (WEdit * edit)
+{
+ if (edit->buffer.curs_line != 0)
+ {
+ edit_cursor_move (edit, -edit->buffer.curs1);
+ edit_move_to_prev_col (edit, 0);
+ edit->force |= REDRAW_PAGE;
+ edit->search_start = 0;
+ edit_update_curs_row (edit);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** goto end of text */
+
+static void
+edit_move_to_bottom (WEdit * edit)
+{
+ if (edit->buffer.curs_line < edit->buffer.lines)
+ {
+ edit_move_down (edit, edit->buffer.lines - edit->curs_row, FALSE);
+ edit->start_display = edit->buffer.size;
+ edit->start_line = edit->buffer.lines;
+ edit_scroll_upward (edit, WIDGET (edit)->rect.lines - 1);
+ edit->force |= REDRAW_PAGE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** goto beginning of line */
+
+static void
+edit_cursor_to_bol (WEdit * edit)
+{
+ edit_cursor_move (edit, edit_buffer_get_current_bol (&edit->buffer) - edit->buffer.curs1);
+ edit->search_start = edit->buffer.curs1;
+ edit->prev_col = edit_get_col (edit);
+ edit->over_col = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** goto end of line */
+
+static void
+edit_cursor_to_eol (WEdit * edit)
+{
+ edit_cursor_move (edit, edit_buffer_get_current_eol (&edit->buffer) - edit->buffer.curs1);
+ edit->search_start = edit->buffer.curs1;
+ edit->prev_col = edit_get_col (edit);
+ edit->over_col = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static unsigned long
+my_type_of (int c)
+{
+ unsigned long x, r = 0;
+ const char *p, *q;
+ const char chars_move_whole_word[] =
+ "!=&|<>^~ !:;, !'!`!.?!\"!( !) !{ !} !Aa0 !+-*/= |<> ![ !] !\\#! ";
+
+ if (c == 0)
+ return 0;
+ if (c == '!')
+ return 2;
+
+ if (g_ascii_isupper ((gchar) c))
+ c = 'A';
+ else if (g_ascii_islower ((gchar) c))
+ c = 'a';
+ else if (g_ascii_isalpha (c))
+ c = 'a';
+ else if (isdigit (c))
+ c = '0';
+ else if (isspace (c))
+ c = ' ';
+ q = strchr (chars_move_whole_word, c);
+ if (!q)
+ return 0xFFFFFFFFUL;
+ do
+ {
+ for (x = 1, p = chars_move_whole_word; p < q; p++)
+ if (*p == '!')
+ x <<= 1;
+ r |= x;
+ }
+ while ((q = strchr (q + 1, c)));
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_left_word_move (WEdit * edit, int s)
+{
+ while (TRUE)
+ {
+ int c1, c2;
+
+ if (edit->column_highlight
+ && edit->mark1 != edit->mark2
+ && edit->over_col == 0
+ && edit->buffer.curs1 == edit_buffer_get_current_bol (&edit->buffer))
+ break;
+ edit_cursor_move (edit, -1);
+ if (edit->buffer.curs1 == 0)
+ break;
+ c1 = edit_buffer_get_previous_byte (&edit->buffer);
+ c2 = edit_buffer_get_current_byte (&edit->buffer);
+ if (c1 == '\n' || c2 == '\n')
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ if (isspace (c1) && !isspace (c2))
+ break;
+ if (s != 0 && !isspace (c1) && isspace (c2))
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_left_word_move_cmd (WEdit * edit)
+{
+ edit_left_word_move (edit, 0);
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_right_word_move (WEdit * edit, int s)
+{
+ while (TRUE)
+ {
+ int c1, c2;
+
+ if (edit->column_highlight
+ && edit->mark1 != edit->mark2
+ && edit->over_col == 0
+ && edit->buffer.curs1 == edit_buffer_get_current_eol (&edit->buffer))
+ break;
+ edit_cursor_move (edit, 1);
+ if (edit->buffer.curs1 >= edit->buffer.size)
+ break;
+ c1 = edit_buffer_get_previous_byte (&edit->buffer);
+ c2 = edit_buffer_get_current_byte (&edit->buffer);
+ if (c1 == '\n' || c2 == '\n')
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ if (isspace (c1) && !isspace (c2))
+ break;
+ if (s != 0 && !isspace (c1) && isspace (c2))
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_right_word_move_cmd (WEdit * edit)
+{
+ edit_right_word_move (edit, 0);
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_right_char_move_cmd (WEdit * edit)
+{
+ int char_length = 1;
+ int c;
+
+#ifdef HAVE_CHARSET
+ if (edit->utf8)
+ {
+ c = edit_buffer_get_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length < 1)
+ char_length = 1;
+ }
+ else
+#endif
+ c = edit_buffer_get_current_byte (&edit->buffer);
+
+ if (edit_options.cursor_beyond_eol && c == '\n')
+ edit->over_col++;
+ else
+ edit_cursor_move (edit, char_length);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_left_char_move_cmd (WEdit * edit)
+{
+ int char_length = 1;
+
+ if (edit->column_highlight
+ && edit_options.cursor_beyond_eol
+ && edit->mark1 != edit->mark2
+ && edit->over_col == 0 && edit->buffer.curs1 == edit_buffer_get_current_bol (&edit->buffer))
+ return;
+#ifdef HAVE_CHARSET
+ if (edit->utf8)
+ {
+ edit_buffer_get_prev_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length < 1)
+ char_length = 1;
+ }
+#endif
+
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit->over_col--;
+ else
+ edit_cursor_move (edit, -char_length);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Up or down cursor moving.
+ direction = TRUE - move up
+ = FALSE - move down
+*/
+
+static void
+edit_move_updown (WEdit * edit, long lines, gboolean do_scroll, gboolean direction)
+{
+ long p;
+ long l = direction ? edit->buffer.curs_line : edit->buffer.lines - edit->buffer.curs_line;
+
+ if (lines > l)
+ lines = l;
+
+ if (lines == 0)
+ return;
+
+ if (lines > 1)
+ edit->force |= REDRAW_PAGE;
+ if (do_scroll)
+ {
+ if (direction)
+ edit_scroll_upward (edit, lines);
+ else
+ edit_scroll_downward (edit, lines);
+ }
+ p = edit_buffer_get_current_bol (&edit->buffer);
+ p = direction ? edit_buffer_get_backward_offset (&edit->buffer, p, lines) :
+ edit_buffer_get_forward_offset (&edit->buffer, p, lines, 0);
+ edit_cursor_move (edit, p - edit->buffer.curs1);
+ edit_move_to_prev_col (edit, p);
+
+#ifdef HAVE_CHARSET
+ /* search start of current multibyte char (like CJK) */
+ if (edit->buffer.curs1 > 0 && edit->buffer.curs1 + 1 < edit->buffer.size
+ && edit_buffer_get_current_byte (&edit->buffer) >= 256)
+ {
+ edit_right_char_move_cmd (edit);
+ edit_left_char_move_cmd (edit);
+ }
+#endif
+
+ edit->search_start = edit->buffer.curs1;
+ edit->found_len = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_right_delete_word (WEdit * edit)
+{
+ while (edit->buffer.curs1 < edit->buffer.size)
+ {
+ int c1, c2;
+
+ c1 = edit_delete (edit, TRUE);
+ c2 = edit_buffer_get_current_byte (&edit->buffer);
+ if (c1 == '\n' || c2 == '\n')
+ break;
+ if ((isspace (c1) == 0) != (isspace (c2) == 0))
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_left_delete_word (WEdit * edit)
+{
+ while (edit->buffer.curs1 > 0)
+ {
+ int c1, c2;
+
+ c1 = edit_backspace (edit, TRUE);
+ c2 = edit_buffer_get_previous_byte (&edit->buffer);
+ if (c1 == '\n' || c2 == '\n')
+ break;
+ if ((isspace (c1) == 0) != (isspace (c2) == 0))
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ the start column position is not recorded, and hence does not
+ undo as it happed. But who would notice.
+ */
+
+static void
+edit_do_undo (WEdit * edit)
+{
+ long ac;
+ long count = 0;
+
+ edit->undo_stack_disable = 1; /* don't record undo's onto undo stack! */
+ edit->over_col = 0;
+ while ((ac = edit_pop_undo_action (edit)) < KEY_PRESS)
+ {
+ switch ((int) ac)
+ {
+ case STACK_BOTTOM:
+ goto done_undo;
+ case CURS_RIGHT:
+ edit_cursor_move (edit, 1);
+ break;
+ case CURS_LEFT:
+ edit_cursor_move (edit, -1);
+ break;
+ case BACKSPACE:
+ case BACKSPACE_BR:
+ edit_backspace (edit, TRUE);
+ break;
+ case DELCHAR:
+ case DELCHAR_BR:
+ edit_delete (edit, TRUE);
+ break;
+ case COLUMN_ON:
+ edit->column_highlight = 1;
+ break;
+ case COLUMN_OFF:
+ edit->column_highlight = 0;
+ break;
+ default:
+ break;
+ }
+ if (ac >= 256 && ac < 512)
+ edit_insert_ahead (edit, ac - 256);
+ if (ac >= 0 && ac < 256)
+ edit_insert (edit, ac);
+
+ if (ac >= MARK_1 - 2 && ac < MARK_2 - 2)
+ {
+ edit->mark1 = ac - MARK_1;
+ edit->column1 =
+ (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, edit->mark1),
+ 0, edit->mark1);
+ }
+ if (ac >= MARK_2 - 2 && ac < MARK_CURS - 2)
+ {
+ edit->mark2 = ac - MARK_2;
+ edit->column2 =
+ (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, edit->mark2),
+ 0, edit->mark2);
+ }
+ else if (ac >= MARK_CURS - 2 && ac < KEY_PRESS)
+ {
+ edit->end_mark_curs = ac - MARK_CURS;
+ }
+ if (count++)
+ edit->force |= REDRAW_PAGE; /* more than one pop usually means something big */
+ }
+
+ if (edit->start_display > ac - KEY_PRESS)
+ {
+ edit->start_line -=
+ edit_buffer_count_lines (&edit->buffer, ac - KEY_PRESS, edit->start_display);
+ edit->force |= REDRAW_PAGE;
+ }
+ else if (edit->start_display < ac - KEY_PRESS)
+ {
+ edit->start_line +=
+ edit_buffer_count_lines (&edit->buffer, edit->start_display, ac - KEY_PRESS);
+ edit->force |= REDRAW_PAGE;
+ }
+ edit->start_display = ac - KEY_PRESS; /* see push and pop above */
+ edit_update_curs_row (edit);
+
+ done_undo:
+ edit->undo_stack_disable = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_do_redo (WEdit * edit)
+{
+ long ac;
+ long count = 0;
+
+ if (edit->redo_stack_reset)
+ return;
+
+ edit->over_col = 0;
+ while ((ac = edit_pop_redo_action (edit)) < KEY_PRESS)
+ {
+ switch ((int) ac)
+ {
+ case STACK_BOTTOM:
+ goto done_redo;
+ case CURS_RIGHT:
+ edit_cursor_move (edit, 1);
+ break;
+ case CURS_LEFT:
+ edit_cursor_move (edit, -1);
+ break;
+ case BACKSPACE:
+ edit_backspace (edit, TRUE);
+ break;
+ case DELCHAR:
+ edit_delete (edit, TRUE);
+ break;
+ case COLUMN_ON:
+ edit->column_highlight = 1;
+ break;
+ case COLUMN_OFF:
+ edit->column_highlight = 0;
+ break;
+ default:
+ break;
+ }
+ if (ac >= 256 && ac < 512)
+ edit_insert_ahead (edit, ac - 256);
+ if (ac >= 0 && ac < 256)
+ edit_insert (edit, ac);
+
+ if (ac >= MARK_1 - 2 && ac < MARK_2 - 2)
+ {
+ edit->mark1 = ac - MARK_1;
+ edit->column1 =
+ (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, edit->mark1),
+ 0, edit->mark1);
+ }
+ else if (ac >= MARK_2 - 2 && ac < KEY_PRESS)
+ {
+ edit->mark2 = ac - MARK_2;
+ edit->column2 =
+ (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, edit->mark2),
+ 0, edit->mark2);
+ }
+ /* more than one pop usually means something big */
+ if (count++)
+ edit->force |= REDRAW_PAGE;
+ }
+
+ if (edit->start_display > ac - KEY_PRESS)
+ {
+ edit->start_line -=
+ edit_buffer_count_lines (&edit->buffer, ac - KEY_PRESS, edit->start_display);
+ edit->force |= REDRAW_PAGE;
+ }
+ else if (edit->start_display < ac - KEY_PRESS)
+ {
+ edit->start_line +=
+ edit_buffer_count_lines (&edit->buffer, edit->start_display, ac - KEY_PRESS);
+ edit->force |= REDRAW_PAGE;
+ }
+ edit->start_display = ac - KEY_PRESS; /* see push and pop above */
+ edit_update_curs_row (edit);
+
+ done_redo:
+ ;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_group_undo (WEdit * edit)
+{
+ long ac = KEY_PRESS;
+ long cur_ac = KEY_PRESS;
+ while (ac != STACK_BOTTOM && ac == cur_ac)
+ {
+ cur_ac = get_prev_undo_action (edit);
+ edit_do_undo (edit);
+ ac = get_prev_undo_action (edit);
+ /* exit from cycle if edit_options.group_undo is not set,
+ * and make single UNDO operation
+ */
+ if (!edit_options.group_undo)
+ ac = STACK_BOTTOM;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_delete_to_line_end (WEdit * edit)
+{
+ while (edit_buffer_get_current_byte (&edit->buffer) != '\n' && edit->buffer.curs2 != 0)
+ edit_delete (edit, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_delete_to_line_begin (WEdit * edit)
+{
+ while (edit_buffer_get_previous_byte (&edit->buffer) != '\n' && edit->buffer.curs1 != 0)
+ edit_backspace (edit, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_aligned_on_a_tab (WEdit * edit)
+{
+ long curs_col;
+
+ edit_update_curs_col (edit);
+ curs_col = edit->curs_col % (TAB_SIZE * space_width);
+ return (curs_col == 0 || curs_col == (HALF_TAB_SIZE * space_width));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+right_of_four_spaces (WEdit * edit)
+{
+ int i, ch = 0;
+
+ for (i = 1; i <= HALF_TAB_SIZE; i++)
+ ch |= edit_buffer_get_byte (&edit->buffer, edit->buffer.curs1 - i);
+
+ return (ch == ' ' && is_aligned_on_a_tab (edit));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+left_of_four_spaces (WEdit * edit)
+{
+ int i, ch = 0;
+
+ for (i = 0; i < HALF_TAB_SIZE; i++)
+ ch |= edit_buffer_get_byte (&edit->buffer, edit->buffer.curs1 + i);
+
+ return (ch == ' ' && is_aligned_on_a_tab (edit));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_auto_indent (WEdit * edit)
+{
+ off_t p;
+
+ p = edit->buffer.curs1;
+ /* use the previous line as a template */
+ p = edit_buffer_get_backward_offset (&edit->buffer, p, 1);
+ /* copy the leading whitespace of the line */
+ while (TRUE)
+ { /* no range check - the line _is_ \n-terminated */
+ char c;
+
+ c = edit_buffer_get_byte (&edit->buffer, p++);
+ if (!whitespace (c))
+ break;
+ edit_insert (edit, c);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_double_newline (WEdit * edit)
+{
+ edit_insert (edit, '\n');
+ if (edit_buffer_get_current_byte (&edit->buffer) == '\n'
+ || edit_buffer_get_byte (&edit->buffer, edit->buffer.curs1 - 2) == '\n')
+ return;
+ edit->force |= REDRAW_PAGE;
+ edit_insert (edit, '\n');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+insert_spaces_tab (WEdit * edit, gboolean half)
+{
+ long i;
+
+ edit_update_curs_col (edit);
+ i = TAB_SIZE * space_width;
+ if (half)
+ i /= 2;
+ if (i != 0)
+ {
+ i = ((edit->curs_col / i) + 1) * i - edit->curs_col;
+ while (i > 0)
+ {
+ edit_insert (edit, ' ');
+ i -= space_width;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_tab_cmd (WEdit * edit)
+{
+ if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer))
+ {
+ /* insert a half tab (usually four spaces) unless there is a
+ half tab already behind, then delete it and insert a
+ full tab. */
+ if (edit_options.fill_tabs_with_spaces || !right_of_four_spaces (edit))
+ insert_spaces_tab (edit, TRUE);
+ else
+ {
+ int i;
+
+ for (i = 1; i <= HALF_TAB_SIZE; i++)
+ edit_backspace (edit, TRUE);
+ edit_insert (edit, '\t');
+ }
+ }
+ else if (edit_options.fill_tabs_with_spaces)
+ insert_spaces_tab (edit, FALSE);
+ else
+ edit_insert (edit, '\t');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+check_and_wrap_line (WEdit * edit)
+{
+ off_t curs;
+
+ if (!edit_options.typewriter_wrap)
+ return;
+ edit_update_curs_col (edit);
+ if (edit->curs_col < edit_options.word_wrap_line_length)
+ return;
+ curs = edit->buffer.curs1;
+ while (TRUE)
+ {
+ int c;
+
+ curs--;
+ c = edit_buffer_get_byte (&edit->buffer, curs);
+ if (c == '\n' || curs <= 0)
+ {
+ edit_insert (edit, '\n');
+ return;
+ }
+ if (whitespace (c))
+ {
+ off_t current = edit->buffer.curs1;
+ edit_cursor_move (edit, curs - edit->buffer.curs1 + 1);
+ edit_insert (edit, '\n');
+ edit_cursor_move (edit, current - edit->buffer.curs1 + 1);
+ return;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** this find the matching bracket in either direction, and sets edit->bracket
+ *
+ * @param edit editor object
+ * @param in_screen search only on the current screen
+ * @param furthest_bracket_search count of the bytes for search
+ *
+ * @return position of the found bracket (-1 if no match)
+ */
+
+static off_t
+edit_get_bracket (WEdit * edit, gboolean in_screen, unsigned long furthest_bracket_search)
+{
+ const char *const b = "{}{[][()(", *p;
+ int i = 1, inc = -1, c, d, n = 0;
+ unsigned long j = 0;
+ off_t q;
+
+ edit_update_curs_row (edit);
+ c = edit_buffer_get_current_byte (&edit->buffer);
+ p = strchr (b, c);
+ /* not on a bracket at all */
+ if (p == NULL || *p == '\0')
+ return -1;
+ /* the matching bracket */
+ d = p[1];
+ /* going left or right? */
+ if (strchr ("{[(", c) != NULL)
+ inc = 1;
+ /* no limit */
+ if (furthest_bracket_search == 0)
+ furthest_bracket_search--; /* ULONG_MAX */
+ for (q = edit->buffer.curs1 + inc;; q += inc)
+ {
+ int a;
+
+ /* out of buffer? */
+ if (q >= edit->buffer.size || q < 0)
+ break;
+ a = edit_buffer_get_byte (&edit->buffer, q);
+ /* don't want to eat CPU */
+ if (j++ > furthest_bracket_search)
+ break;
+ /* out of screen? */
+ if (in_screen)
+ {
+ if (q < edit->start_display)
+ break;
+ /* count lines if searching downward */
+ if (inc > 0 && a == '\n')
+ if (n++ >= WIDGET (edit)->rect.lines - edit->curs_row) /* out of screen */
+ break;
+ }
+ /* count bracket depth */
+ i += (a == c) - (a == d);
+ /* return if bracket depth is zero */
+ if (i == 0)
+ return q;
+ }
+ /* no match */
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_goto_matching_bracket (WEdit * edit)
+{
+ off_t q;
+
+ q = edit_get_bracket (edit, 0, 0);
+ if (q >= 0)
+ {
+ edit->bracket = edit->buffer.curs1;
+ edit->force |= REDRAW_PAGE;
+ edit_cursor_move (edit, q - edit->buffer.curs1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_move_block_to_right (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+ long cur_bol, start_bol;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return;
+
+ start_bol = edit_buffer_get_bol (&edit->buffer, start_mark);
+ cur_bol = edit_buffer_get_bol (&edit->buffer, end_mark - 1);
+
+ do
+ {
+ edit_cursor_move (edit, cur_bol - edit->buffer.curs1);
+ if (!edit_line_is_blank (edit, edit->buffer.curs_line))
+ {
+ if (edit_options.fill_tabs_with_spaces)
+ insert_spaces_tab (edit, edit_options.fake_half_tabs);
+ else
+ edit_insert (edit, '\t');
+ edit_cursor_move (edit,
+ edit_buffer_get_bol (&edit->buffer, cur_bol) - edit->buffer.curs1);
+ }
+
+ if (cur_bol == 0)
+ break;
+
+ cur_bol = edit_buffer_get_bol (&edit->buffer, cur_bol - 1);
+ }
+ while (cur_bol >= start_bol);
+
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_move_block_to_left (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+ off_t cur_bol, start_bol;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return;
+
+ start_bol = edit_buffer_get_bol (&edit->buffer, start_mark);
+ cur_bol = edit_buffer_get_bol (&edit->buffer, end_mark - 1);
+
+ do
+ {
+ int del_tab_width;
+ int next_char;
+
+ edit_cursor_move (edit, cur_bol - edit->buffer.curs1);
+
+ del_tab_width = edit_options.fake_half_tabs ? HALF_TAB_SIZE : TAB_SIZE;
+
+ next_char = edit_buffer_get_current_byte (&edit->buffer);
+ if (next_char == '\t')
+ edit_delete (edit, TRUE);
+ else if (next_char == ' ')
+ {
+ int i;
+
+ for (i = 0; i < del_tab_width; i++)
+ {
+ if (next_char == ' ')
+ edit_delete (edit, TRUE);
+ next_char = edit_buffer_get_current_byte (&edit->buffer);
+ }
+ }
+
+ if (cur_bol == 0)
+ break;
+
+ cur_bol = edit_buffer_get_bol (&edit->buffer, cur_bol - 1);
+ }
+ while (cur_bol >= start_bol);
+
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * prints at the cursor
+ * @return number of chars printed
+ */
+
+static size_t
+edit_print_string (WEdit * e, const char *s)
+{
+ size_t i = 0;
+
+ while (s[i] != '\0')
+ edit_execute_cmd (e, CK_InsertChar, (unsigned char) s[i++]);
+ e->force |= REDRAW_COMPLETELY;
+ edit_update_screen (e);
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+edit_insert_column_from_file (WEdit * edit, int file, off_t * start_pos, off_t * end_pos,
+ long *col1, long *col2)
+{
+ off_t cursor;
+ long col;
+ off_t blocklen = -1, width = 0;
+ unsigned char *data;
+
+ cursor = edit->buffer.curs1;
+ col = edit_get_col (edit);
+ data = g_malloc0 (TEMP_BUF_LEN);
+
+ while ((blocklen = mc_read (file, (char *) data, TEMP_BUF_LEN)) > 0)
+ {
+ off_t i;
+ char *pn;
+
+ pn = strchr ((char *) data, '\n');
+ width = pn == NULL ? blocklen : pn - (char *) data;
+
+ for (i = 0; i < blocklen; i++)
+ {
+ if (data[i] != '\n')
+ edit_insert (edit, data[i]);
+ else
+ { /* fill in and move to next line */
+ long l;
+ off_t p;
+
+ if (edit_buffer_get_current_byte (&edit->buffer) != '\n')
+ for (l = width - (edit_get_col (edit) - col); l > 0; l -= space_width)
+ edit_insert (edit, ' ');
+
+ for (p = edit->buffer.curs1;; p++)
+ {
+ if (p == edit->buffer.size)
+ {
+ edit_cursor_move (edit, edit->buffer.size - edit->buffer.curs1);
+ edit_insert_ahead (edit, '\n');
+ p++;
+ break;
+ }
+ if (edit_buffer_get_byte (&edit->buffer, p) == '\n')
+ {
+ p++;
+ break;
+ }
+ }
+
+ edit_cursor_move (edit, edit_move_forward3 (edit, p, col, 0) - edit->buffer.curs1);
+
+ for (l = col - edit_get_col (edit); l >= space_width; l -= space_width)
+ edit_insert (edit, ' ');
+ }
+ }
+ }
+ *col1 = col;
+ *col2 = col + width;
+ *start_pos = cursor;
+ *end_pos = edit->buffer.curs1;
+ edit_cursor_move (edit, cursor - edit->buffer.curs1);
+ g_free (data);
+
+ return blocklen;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** User edit menu, like user menu (F2) but only in editor. */
+
+void
+user_menu (WEdit * edit, const char *menu_file, int selected_entry)
+{
+ char *block_file;
+ gboolean nomark;
+ off_t curs;
+ off_t start_mark, end_mark;
+ struct stat status;
+ vfs_path_t *block_file_vpath;
+
+ block_file = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
+ block_file_vpath = vfs_path_from_str (block_file);
+ curs = edit->buffer.curs1;
+ nomark = !eval_marks (edit, &start_mark, &end_mark);
+ if (!nomark)
+ edit_save_block (edit, block_file, start_mark, end_mark);
+
+ /* run shell scripts from menu */
+ if (user_menu_cmd (CONST_WIDGET (edit), menu_file, selected_entry)
+ && (mc_stat (block_file_vpath, &status) == 0) && (status.st_size != 0))
+ {
+ int rc = 0;
+ FILE *fd;
+
+ /* i.e. we have marked block */
+ if (!nomark)
+ rc = edit_block_delete_cmd (edit);
+
+ if (rc == 0)
+ {
+ off_t ins_len;
+
+ ins_len = edit_insert_file (edit, block_file_vpath);
+ if (!nomark && ins_len > 0)
+ edit_set_markers (edit, start_mark, start_mark + ins_len, 0, 0);
+ }
+ /* truncate block file */
+ fd = fopen (block_file, "w");
+ if (fd != NULL)
+ fclose (fd);
+ }
+ g_free (block_file);
+ vfs_path_free (block_file_vpath, TRUE);
+
+ edit_cursor_move (edit, curs - edit->buffer.curs1);
+ edit->force |= REDRAW_PAGE;
+ widget_draw (WIDGET (edit));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+edit_get_write_filter (const vfs_path_t * write_name_vpath, const vfs_path_t * filename_vpath)
+{
+ int i;
+ const char *write_name;
+ char *p, *write_name_quoted;
+
+ i = edit_find_filter (filename_vpath);
+ if (i < 0)
+ return NULL;
+
+ write_name = vfs_path_get_last_path_str (write_name_vpath);
+ write_name_quoted = name_quote (write_name, FALSE);
+ p = g_strdup_printf (all_filters[i].write, write_name_quoted);
+ g_free (write_name_quoted);
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * @param edit editor object
+ * @param f value of stream file
+ * @return the length of the file
+ */
+
+off_t
+edit_write_stream (WEdit * edit, FILE * f)
+{
+ long i;
+
+ if (edit->lb == LB_ASIS)
+ {
+ for (i = 0; i < edit->buffer.size; i++)
+ if (fputc (edit_buffer_get_byte (&edit->buffer, i), f) < 0)
+ break;
+ return i;
+ }
+
+ /* change line breaks */
+ for (i = 0; i < edit->buffer.size; i++)
+ {
+ unsigned char c;
+
+ c = edit_buffer_get_byte (&edit->buffer, i);
+ if (!(c == '\n' || c == '\r'))
+ {
+ /* not line break */
+ if (fputc (c, f) < 0)
+ return i;
+ }
+ else
+ { /* (c == '\n' || c == '\r') */
+ unsigned char c1;
+
+ c1 = edit_buffer_get_byte (&edit->buffer, i + 1); /* next char */
+
+ switch (edit->lb)
+ {
+ case LB_UNIX: /* replace "\r\n" or '\r' to '\n' */
+ /* put one line break unconditionally */
+ if (fputc ('\n', f) < 0)
+ return i;
+
+ i++; /* 2 chars are processed */
+
+ if (c == '\r' && c1 == '\n')
+ /* Windows line break; go to the next char */
+ break;
+
+ if (c == '\r' && c1 == '\r')
+ {
+ /* two Macintosh line breaks; put second line break */
+ if (fputc ('\n', f) < 0)
+ return i;
+ break;
+ }
+
+ if (fputc (c1, f) < 0)
+ return i;
+ break;
+
+ case LB_WIN: /* replace '\n' or '\r' to "\r\n" */
+ /* put one line break unconditionally */
+ if (fputc ('\r', f) < 0 || fputc ('\n', f) < 0)
+ return i;
+
+ if (c == '\r' && c1 == '\n')
+ /* Windows line break; go to the next char */
+ i++;
+ break;
+
+ case LB_MAC: /* replace "\r\n" or '\n' to '\r' */
+ /* put one line break unconditionally */
+ if (fputc ('\r', f) < 0)
+ return i;
+
+ i++; /* 2 chars are processed */
+
+ if (c == '\r' && c1 == '\n')
+ /* Windows line break; go to the next char */
+ break;
+
+ if (c == '\n' && c1 == '\n')
+ {
+ /* two Windows line breaks; put second line break */
+ if (fputc ('\r', f) < 0)
+ return i;
+ break;
+ }
+
+ if (fputc (c1, f) < 0)
+ return i;
+ break;
+ case LB_ASIS: /* default without changes */
+ default:
+ break;
+ }
+ }
+ }
+
+ return edit->buffer.size;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+is_break_char (char c)
+{
+ return (isspace (c) || strchr ("{}[]()<>=|/\\!?~-+`'\",.;:#$%^&*", c));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** inserts a file at the cursor, returns count of inserted bytes on success */
+
+off_t
+edit_insert_file (WEdit * edit, const vfs_path_t * filename_vpath)
+{
+ char *p;
+ off_t current;
+ off_t ins_len = 0;
+
+ p = edit_get_filter (filename_vpath);
+ current = edit->buffer.curs1;
+
+ if (p != NULL)
+ {
+ FILE *f;
+
+ f = (FILE *) popen (p, "r");
+ if (f != NULL)
+ {
+ edit_insert_stream (edit, f);
+
+ /* Place cursor at the end of text selection */
+ if (!edit_options.cursor_after_inserted_block)
+ {
+ ins_len = edit->buffer.curs1 - current;
+ edit_cursor_move (edit, -ins_len);
+ }
+ if (pclose (f) > 0)
+ {
+ char *errmsg;
+
+ errmsg = g_strdup_printf (_("Error reading from pipe: %s"), p);
+ edit_error_dialog (_("Error"), errmsg);
+ g_free (errmsg);
+ ins_len = -1;
+ }
+ }
+ else
+ {
+ char *errmsg;
+
+ errmsg = g_strdup_printf (_("Cannot open pipe for reading: %s"), p);
+ edit_error_dialog (_("Error"), errmsg);
+ g_free (errmsg);
+ ins_len = -1;
+ }
+ g_free (p);
+ }
+ else
+ {
+ int file;
+ off_t blocklen;
+ int vertical_insertion = 0;
+ char *buf;
+
+ file = mc_open (filename_vpath, O_RDONLY | O_BINARY);
+ if (file == -1)
+ return -1;
+
+ buf = g_malloc0 (TEMP_BUF_LEN);
+ blocklen = mc_read (file, buf, sizeof (VERTICAL_MAGIC));
+ if (blocklen > 0)
+ {
+ /* if contain signature VERTICAL_MAGIC then it vertical block */
+ if (memcmp (buf, VERTICAL_MAGIC, sizeof (VERTICAL_MAGIC)) == 0)
+ vertical_insertion = 1;
+ else
+ mc_lseek (file, 0, SEEK_SET);
+ }
+
+ if (vertical_insertion)
+ {
+ off_t mark1, mark2;
+ long c1, c2;
+
+ blocklen = edit_insert_column_from_file (edit, file, &mark1, &mark2, &c1, &c2);
+ edit_set_markers (edit, edit->buffer.curs1, mark2, c1, c2);
+
+ /* highlight inserted text then not persistent blocks */
+ if (!edit_options.persistent_selections && edit->modified)
+ {
+ if (!edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_OFF);
+ edit->column_highlight = 1;
+ }
+ }
+ else
+ {
+ off_t i;
+
+ while ((blocklen = mc_read (file, (char *) buf, TEMP_BUF_LEN)) > 0)
+ {
+ for (i = 0; i < blocklen; i++)
+ edit_insert (edit, buf[i]);
+ }
+ /* highlight inserted text then not persistent blocks */
+ if (!edit_options.persistent_selections && edit->modified)
+ {
+ edit_set_markers (edit, edit->buffer.curs1, current, 0, 0);
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ }
+
+ /* Place cursor at the end of text selection */
+ if (!edit_options.cursor_after_inserted_block)
+ {
+ ins_len = edit->buffer.curs1 - current;
+ edit_cursor_move (edit, -ins_len);
+ }
+ }
+
+ edit->force |= REDRAW_PAGE;
+ g_free (buf);
+ mc_close (file);
+ if (blocklen != 0)
+ ins_len = 0;
+ }
+
+ return ins_len;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Fill in the edit structure. Return NULL on failure. Pass edit as
+ * NULL to allocate a new structure.
+ *
+ * If line is 0, try to restore saved position. Otherwise put the
+ * cursor on that line and show it in the middle of the screen.
+ */
+
+WEdit *
+edit_init (WEdit * edit, const WRect * r, const vfs_path_t * filename_vpath, long line)
+{
+ gboolean to_free = FALSE;
+
+ auto_syntax = TRUE; /* Resetting to auto on every invocation */
+ edit_options.line_state_width = edit_options.line_state ? LINE_STATE_WIDTH : 0;
+
+ if (edit != NULL)
+ {
+ gboolean fullscreen;
+ WRect loc_prev;
+
+ /* save some widget parameters */
+ fullscreen = edit->fullscreen;
+ loc_prev = edit->loc_prev;
+
+ edit_purge_widget (edit);
+
+ /* restore saved parameters */
+ edit->fullscreen = fullscreen;
+ edit->loc_prev = loc_prev;
+ }
+ else
+ {
+ Widget *w;
+
+ edit = g_malloc0 (sizeof (WEdit));
+ to_free = TRUE;
+
+ w = WIDGET (edit);
+ widget_init (w, r, NULL, NULL);
+ w->options |= WOP_SELECTABLE | WOP_TOP_SELECT | WOP_WANT_CURSOR;
+ w->keymap = editor_map;
+ w->ext_keymap = editor_x_map;
+ edit->fullscreen = TRUE;
+ edit_save_size (edit);
+ }
+
+ edit->drag_state = MCEDIT_DRAG_NONE;
+
+ edit->stat1.st_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+ edit->stat1.st_uid = getuid ();
+ edit->stat1.st_gid = getgid ();
+ edit->stat1.st_mtime = 0;
+
+ edit->over_col = 0;
+ edit->bracket = -1;
+ edit->last_bracket = -1;
+ edit->force |= REDRAW_PAGE;
+
+ /* set file name before load file */
+ edit_set_filename (edit, filename_vpath);
+
+ edit->undo_stack_size = START_STACK_SIZE;
+ edit->undo_stack_size_mask = START_STACK_SIZE - 1;
+ edit->undo_stack = g_malloc0 ((edit->undo_stack_size + 10) * sizeof (long));
+
+ edit->redo_stack_size = START_STACK_SIZE;
+ edit->redo_stack_size_mask = START_STACK_SIZE - 1;
+ edit->redo_stack = g_malloc0 ((edit->redo_stack_size + 10) * sizeof (long));
+
+#ifdef HAVE_CHARSET
+ edit->utf8 = FALSE;
+ edit->converter = str_cnv_from_term;
+ edit_set_codeset (edit);
+#endif
+
+ if (!edit_load_file (edit))
+ {
+ /* edit_load_file already gives an error message */
+ if (to_free)
+ g_free (edit);
+ return NULL;
+ }
+
+ edit->loading_done = 1;
+ edit->modified = 0;
+ edit->locked = 0;
+ edit_load_syntax (edit, NULL, NULL);
+ edit_get_syntax_color (edit, -1);
+
+ /* load saved cursor position and/or boolmarks */
+ if ((line == 0) && edit_options.save_position)
+ edit_load_position (edit, TRUE);
+ else
+ {
+ edit_load_position (edit, FALSE);
+ if (line <= 0)
+ line = 1;
+ edit_move_display (edit, line - 1);
+ edit_move_to_line (edit, line - 1);
+ }
+
+ edit_load_macro_cmd (edit);
+
+ return edit;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Clear the edit struct, freeing everything in it. Return TRUE on success */
+gboolean
+edit_clean (WEdit * edit)
+{
+ if (edit == NULL)
+ return FALSE;
+
+ /* a stale lock, remove it */
+ if (edit->locked)
+ (void) unlock_file (edit->filename_vpath);
+
+ /* save cursor position */
+ if (edit_options.save_position)
+ edit_save_position (edit);
+ else if (edit->serialized_bookmarks != NULL)
+ g_array_free (edit->serialized_bookmarks, TRUE);
+
+ /* File specified on the mcedit command line and never saved */
+ if (edit->delete_file)
+ unlink (vfs_path_get_last_path_str (edit->filename_vpath));
+
+ edit_free_syntax_rules (edit);
+ book_mark_flush (edit, -1);
+
+ edit_buffer_clean (&edit->buffer);
+
+ g_free (edit->undo_stack);
+ g_free (edit->redo_stack);
+ vfs_path_free (edit->filename_vpath, TRUE);
+ vfs_path_free (edit->dir_vpath, TRUE);
+ edit_search_deinit (edit);
+
+#ifdef HAVE_CHARSET
+ if (edit->converter != str_cnv_from_term)
+ str_close_conv (edit->converter);
+#endif
+
+ edit_purge_widget (edit);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Load a new file into the editor and set line. If it fails, preserve the old file.
+ * To do it, allocate a new widget, initialize it and, if the new file
+ * was loaded, copy the data to the old widget.
+ *
+ * @return TRUE on success, FALSE on failure.
+ */
+gboolean
+edit_reload_line (WEdit * edit, const vfs_path_t * filename_vpath, long line)
+{
+ Widget *w = WIDGET (edit);
+ WEdit *e;
+
+ e = g_malloc0 (sizeof (WEdit));
+ *WIDGET (e) = *w;
+ /* save some widget parameters */
+ e->fullscreen = edit->fullscreen;
+ e->loc_prev = edit->loc_prev;
+
+ if (edit_init (e, &w->rect, filename_vpath, line) == NULL)
+ {
+ g_free (e);
+ return FALSE;
+ }
+
+ edit_clean (edit);
+ memcpy (edit, e, sizeof (*edit));
+ g_free (e);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+void
+edit_set_codeset (WEdit * edit)
+{
+ const char *cp_id;
+
+ 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 (edit->converter != str_cnv_from_term)
+ str_close_conv (edit->converter);
+ edit->converter = conv;
+ }
+ }
+
+ if (cp_id != NULL)
+ edit->utf8 = str_isutf8 (cp_id);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Recording stack for undo:
+ * The following is an implementation of a compressed stack. Identical
+ * pushes are recorded by a negative prefix indicating the number of times the
+ * same char was pushed. This saves space for repeated curs-left or curs-right
+ * delete etc.
+ *
+ * eg:
+ *
+ * pushed: stored:
+ *
+ * a
+ * b a
+ * b -3
+ * b b
+ * c --> -4
+ * c c
+ * c d
+ * c
+ * d
+ *
+ * If the stack long int is 0-255 it represents a normal insert (from a backspace),
+ * 256-512 is an insert ahead (from a delete), If it is between 600 and 700 it is one
+ * of the cursor functions define'd in edit-impl.h. 1000 through 700'000'000 is to
+ * set edit->mark1 position. 700'000'000 through 1400'000'000 is to set edit->mark2
+ * position.
+ *
+ * The only way the cursor moves or the buffer is changed is through the routines:
+ * insert, backspace, insert_ahead, delete, and cursor_move.
+ * These record the reverse undo movements onto the stack each time they are
+ * called.
+ *
+ * Each key press results in a set of actions (insert; delete ...). So each time
+ * a key is pressed the current position of start_display is pushed as
+ * KEY_PRESS + start_display. Then for undoing, we pop until we get to a number
+ * over KEY_PRESS. We then assign this number less KEY_PRESS to start_display. So undo
+ * tracks scrolling and key actions exactly. (KEY_PRESS is about (2^31) * (2/3) = 1400'000'000)
+ *
+ *
+ *
+ * @param edit editor object
+ * @param c code of the action
+ */
+
+void
+edit_push_undo_action (WEdit * edit, long c)
+{
+ unsigned long sp = edit->undo_stack_pointer;
+ unsigned long spm1;
+
+ /* first enlarge the stack if necessary */
+ if (sp > edit->undo_stack_size - 10)
+ { /* say */
+ if (max_undo < 256)
+ max_undo = 256;
+ if (edit->undo_stack_size < (unsigned long) max_undo)
+ {
+ long *t;
+
+ t = g_realloc (edit->undo_stack, (edit->undo_stack_size * 2 + 10) * sizeof (long));
+ if (t != NULL)
+ {
+ edit->undo_stack = t;
+ edit->undo_stack_size <<= 1;
+ edit->undo_stack_size_mask = edit->undo_stack_size - 1;
+ }
+ }
+ }
+ spm1 = (edit->undo_stack_pointer - 1) & edit->undo_stack_size_mask;
+ if (edit->undo_stack_disable)
+ {
+ edit_push_redo_action (edit, KEY_PRESS);
+ edit_push_redo_action (edit, c);
+ return;
+ }
+
+ if (edit->redo_stack_reset)
+ edit->redo_stack_bottom = edit->redo_stack_pointer = 0;
+
+ if (edit->undo_stack_bottom != sp
+ && spm1 != edit->undo_stack_bottom
+ && ((sp - 2) & edit->undo_stack_size_mask) != edit->undo_stack_bottom)
+ {
+ long d;
+ if (edit->undo_stack[spm1] < 0)
+ {
+ d = edit->undo_stack[(sp - 2) & edit->undo_stack_size_mask];
+ if (d == c && edit->undo_stack[spm1] > -1000000000)
+ {
+ if (c < KEY_PRESS) /* --> no need to push multiple do-nothings */
+ edit->undo_stack[spm1]--;
+ return;
+ }
+ }
+ else
+ {
+ d = edit->undo_stack[spm1];
+ if (d == c)
+ {
+ if (c >= KEY_PRESS)
+ return; /* --> no need to push multiple do-nothings */
+ edit->undo_stack[sp] = -2;
+ goto check_bottom;
+ }
+ }
+ }
+ edit->undo_stack[sp] = c;
+
+ check_bottom:
+ edit->undo_stack_pointer = (edit->undo_stack_pointer + 1) & edit->undo_stack_size_mask;
+
+ /* if the sp wraps round and catches the undo_stack_bottom then erase
+ * the first set of actions on the stack to make space - by moving
+ * undo_stack_bottom forward one "key press" */
+ c = (edit->undo_stack_pointer + 2) & edit->undo_stack_size_mask;
+ if ((unsigned long) c == edit->undo_stack_bottom ||
+ (((unsigned long) c + 1) & edit->undo_stack_size_mask) == edit->undo_stack_bottom)
+ do
+ {
+ edit->undo_stack_bottom = (edit->undo_stack_bottom + 1) & edit->undo_stack_size_mask;
+ }
+ while (edit->undo_stack[edit->undo_stack_bottom] < KEY_PRESS
+ && edit->undo_stack_bottom != edit->undo_stack_pointer);
+
+ /*If a single key produced enough pushes to wrap all the way round then we would notice that the [undo_stack_bottom] does not contain KEY_PRESS. The stack is then initialised: */
+ if (edit->undo_stack_pointer != edit->undo_stack_bottom
+ && edit->undo_stack[edit->undo_stack_bottom] < KEY_PRESS)
+ {
+ edit->undo_stack_bottom = edit->undo_stack_pointer = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_push_redo_action (WEdit * edit, long c)
+{
+ unsigned long sp = edit->redo_stack_pointer;
+ unsigned long spm1;
+ /* first enlarge the stack if necessary */
+ if (sp > edit->redo_stack_size - 10)
+ { /* say */
+ if (max_undo < 256)
+ max_undo = 256;
+ if (edit->redo_stack_size < (unsigned long) max_undo)
+ {
+ long *t;
+
+ t = g_realloc (edit->redo_stack, (edit->redo_stack_size * 2 + 10) * sizeof (long));
+ if (t != NULL)
+ {
+ edit->redo_stack = t;
+ edit->redo_stack_size <<= 1;
+ edit->redo_stack_size_mask = edit->redo_stack_size - 1;
+ }
+ }
+ }
+ spm1 = (edit->redo_stack_pointer - 1) & edit->redo_stack_size_mask;
+
+ if (edit->redo_stack_bottom != sp
+ && spm1 != edit->redo_stack_bottom
+ && ((sp - 2) & edit->redo_stack_size_mask) != edit->redo_stack_bottom)
+ {
+ long d;
+ if (edit->redo_stack[spm1] < 0)
+ {
+ d = edit->redo_stack[(sp - 2) & edit->redo_stack_size_mask];
+ if (d == c && edit->redo_stack[spm1] > -1000000000)
+ {
+ if (c < KEY_PRESS) /* --> no need to push multiple do-nothings */
+ edit->redo_stack[spm1]--;
+ return;
+ }
+ }
+ else
+ {
+ d = edit->redo_stack[spm1];
+ if (d == c)
+ {
+ if (c >= KEY_PRESS)
+ return; /* --> no need to push multiple do-nothings */
+ edit->redo_stack[sp] = -2;
+ goto redo_check_bottom;
+ }
+ }
+ }
+ edit->redo_stack[sp] = c;
+
+ redo_check_bottom:
+ edit->redo_stack_pointer = (edit->redo_stack_pointer + 1) & edit->redo_stack_size_mask;
+
+ /* if the sp wraps round and catches the redo_stack_bottom then erase
+ * the first set of actions on the stack to make space - by moving
+ * redo_stack_bottom forward one "key press" */
+ c = (edit->redo_stack_pointer + 2) & edit->redo_stack_size_mask;
+ if ((unsigned long) c == edit->redo_stack_bottom ||
+ (((unsigned long) c + 1) & edit->redo_stack_size_mask) == edit->redo_stack_bottom)
+ do
+ {
+ edit->redo_stack_bottom = (edit->redo_stack_bottom + 1) & edit->redo_stack_size_mask;
+ }
+ while (edit->redo_stack[edit->redo_stack_bottom] < KEY_PRESS
+ && edit->redo_stack_bottom != edit->redo_stack_pointer);
+
+ /*
+ * If a single key produced enough pushes to wrap all the way round then
+ * we would notice that the [redo_stack_bottom] does not contain KEY_PRESS.
+ * The stack is then initialised:
+ */
+
+ if (edit->redo_stack_pointer != edit->redo_stack_bottom
+ && edit->redo_stack[edit->redo_stack_bottom] < KEY_PRESS)
+ edit->redo_stack_bottom = edit->redo_stack_pointer = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ Basic low level single character buffer alterations and movements at the cursor.
+ */
+
+void
+edit_insert (WEdit * edit, int c)
+{
+ /* first we must update the position of the display window */
+ if (edit->buffer.curs1 < edit->start_display)
+ {
+ edit->start_display++;
+ if (c == '\n')
+ edit->start_line++;
+ }
+
+ /* Mark file as modified, unless the file hasn't been fully loaded */
+ if (edit->loading_done)
+ edit_modification (edit);
+
+ /* now we must update some info on the file and check if a redraw is required */
+ if (c == '\n')
+ {
+ book_mark_inc (edit, edit->buffer.curs_line);
+ edit->buffer.curs_line++;
+ edit->buffer.lines++;
+ edit->force |= REDRAW_LINE_ABOVE | REDRAW_AFTER_CURSOR;
+ }
+
+ /* save the reverse command onto the undo stack */
+ /* ordinary char and not space */
+ if (c > 32)
+ edit_push_undo_action (edit, BACKSPACE);
+ else
+ edit_push_undo_action (edit, BACKSPACE_BR);
+ /* update markers */
+ edit->mark1 += (edit->mark1 > edit->buffer.curs1) ? 1 : 0;
+ edit->mark2 += (edit->mark2 > edit->buffer.curs1) ? 1 : 0;
+ edit->last_get_rule += (edit->last_get_rule > edit->buffer.curs1) ? 1 : 0;
+
+ edit_buffer_insert (&edit->buffer, c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** same as edit_insert and move left */
+
+void
+edit_insert_ahead (WEdit * edit, int c)
+{
+ if (edit->buffer.curs1 < edit->start_display)
+ {
+ edit->start_display++;
+ if (c == '\n')
+ edit->start_line++;
+ }
+ edit_modification (edit);
+ if (c == '\n')
+ {
+ book_mark_inc (edit, edit->buffer.curs_line);
+ edit->buffer.lines++;
+ edit->force |= REDRAW_AFTER_CURSOR;
+ }
+ /* ordinary char and not space */
+ if (c > 32)
+ edit_push_undo_action (edit, DELCHAR);
+ else
+ edit_push_undo_action (edit, DELCHAR_BR);
+
+ edit->mark1 += (edit->mark1 >= edit->buffer.curs1) ? 1 : 0;
+ edit->mark2 += (edit->mark2 >= edit->buffer.curs1) ? 1 : 0;
+ edit->last_get_rule += (edit->last_get_rule >= edit->buffer.curs1) ? 1 : 0;
+
+ edit_buffer_insert_ahead (&edit->buffer, c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_insert_over (WEdit * edit)
+{
+ long i;
+
+ for (i = 0; i < edit->over_col; i++)
+ edit_insert (edit, ' ');
+
+ edit->over_col = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+edit_delete (WEdit * edit, gboolean byte_delete)
+{
+ int p = 0;
+ int char_length = 1;
+ int i;
+
+ if (edit->buffer.curs2 == 0)
+ return 0;
+
+#ifdef HAVE_CHARSET
+ /* if byte_delete == TRUE then delete only one byte not multibyte char */
+ if (edit->utf8 && !byte_delete)
+ {
+ edit_buffer_get_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length < 1)
+ char_length = 1;
+ }
+#else
+ (void) byte_delete;
+#endif
+
+ if (edit->mark2 != edit->mark1)
+ edit_push_markers (edit);
+
+ for (i = 1; i <= char_length; i++)
+ {
+ if (edit->mark1 > edit->buffer.curs1)
+ {
+ edit->mark1--;
+ edit->end_mark_curs--;
+ }
+ if (edit->mark2 > edit->buffer.curs1)
+ edit->mark2--;
+ if (edit->last_get_rule > edit->buffer.curs1)
+ edit->last_get_rule--;
+
+ p = edit_buffer_delete (&edit->buffer);
+
+ edit_push_undo_action (edit, p + 256);
+ }
+
+ edit_modification (edit);
+ if (p == '\n')
+ {
+ book_mark_dec (edit, edit->buffer.curs_line);
+ edit->buffer.lines--;
+ edit->force |= REDRAW_AFTER_CURSOR;
+ }
+ if (edit->buffer.curs1 < edit->start_display)
+ {
+ edit->start_display--;
+ if (p == '\n')
+ edit->start_line--;
+ }
+
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+edit_backspace (WEdit * edit, gboolean byte_delete)
+{
+ int p = 0;
+ int char_length = 1;
+ int i;
+
+ if (edit->buffer.curs1 == 0)
+ return 0;
+
+ if (edit->mark2 != edit->mark1)
+ edit_push_markers (edit);
+
+#ifdef HAVE_CHARSET
+ if (edit->utf8 && !byte_delete)
+ {
+ edit_buffer_get_prev_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length < 1)
+ char_length = 1;
+ }
+#else
+ (void) byte_delete;
+#endif
+
+ for (i = 1; i <= char_length; i++)
+ {
+ if (edit->mark1 >= edit->buffer.curs1)
+ {
+ edit->mark1--;
+ edit->end_mark_curs--;
+ }
+ if (edit->mark2 >= edit->buffer.curs1)
+ edit->mark2--;
+ if (edit->last_get_rule >= edit->buffer.curs1)
+ edit->last_get_rule--;
+
+ p = edit_buffer_backspace (&edit->buffer);
+
+ edit_push_undo_action (edit, p);
+ }
+ edit_modification (edit);
+ if (p == '\n')
+ {
+ book_mark_dec (edit, edit->buffer.curs_line);
+ edit->buffer.curs_line--;
+ edit->buffer.lines--;
+ edit->force |= REDRAW_AFTER_CURSOR;
+ }
+
+ if (edit->buffer.curs1 < edit->start_display)
+ {
+ edit->start_display--;
+ if (p == '\n')
+ edit->start_line--;
+ }
+
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** moves the cursor right or left: increment positive or negative respectively */
+
+void
+edit_cursor_move (WEdit * edit, off_t increment)
+{
+ if (increment < 0)
+ {
+ for (; increment < 0 && edit->buffer.curs1 != 0; increment++)
+ {
+ int c;
+
+ edit_push_undo_action (edit, CURS_RIGHT);
+
+ c = edit_buffer_get_previous_byte (&edit->buffer);
+ edit_buffer_insert_ahead (&edit->buffer, c);
+ c = edit_buffer_backspace (&edit->buffer);
+ if (c == '\n')
+ {
+ edit->buffer.curs_line--;
+ edit->force |= REDRAW_LINE_BELOW;
+ }
+ }
+ }
+ else
+ {
+ for (; increment > 0 && edit->buffer.curs2 != 0; increment--)
+ {
+ int c;
+
+ edit_push_undo_action (edit, CURS_LEFT);
+
+ c = edit_buffer_get_current_byte (&edit->buffer);
+ edit_buffer_insert (&edit->buffer, c);
+ c = edit_buffer_delete (&edit->buffer);
+ if (c == '\n')
+ {
+ edit->buffer.curs_line++;
+ edit->force |= REDRAW_LINE_ABOVE;
+ }
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* If cols is zero this returns the count of columns from current to upto. */
+/* If upto is zero returns index of cols across from current. */
+
+off_t
+edit_move_forward3 (const WEdit * edit, off_t current, long cols, off_t upto)
+{
+ off_t p, q;
+ long col;
+
+ if (upto != 0)
+ {
+ q = upto;
+ cols = -10;
+ }
+ else
+ q = edit->buffer.size + 2;
+
+ for (col = 0, p = current; p < q; p++)
+ {
+ int c, orig_c;
+
+ if (cols != -10)
+ {
+ if (col == cols)
+ return p;
+ if (col > cols)
+ return p - 1;
+ }
+
+ orig_c = c = edit_buffer_get_byte (&edit->buffer, p);
+
+#ifdef HAVE_CHARSET
+ if (edit->utf8)
+ {
+ int utf_ch;
+ int char_length = 1;
+
+ utf_ch = edit_buffer_get_utf (&edit->buffer, p, &char_length);
+ if (mc_global.utf8_display)
+ {
+ if (char_length > 1)
+ col -= char_length - 1;
+ if (g_unichar_iswide (utf_ch))
+ col++;
+ }
+ else if (char_length > 1 && g_unichar_isprint (utf_ch))
+ col -= char_length - 1;
+ }
+
+ c = convert_to_display_c (c);
+#endif
+
+ if (c == '\n')
+ return (upto != 0 ? (off_t) col : p);
+ if (c == '\t')
+ col += TAB_SIZE - col % TAB_SIZE;
+ else if ((c < 32 || c == 127) && (orig_c == c
+#ifdef HAVE_CHARSET
+ || (!mc_global.utf8_display && !edit->utf8)
+#endif
+ ))
+ /* '\r' is shown as ^M, so we must advance 2 characters */
+ /* Caret notation for control characters */
+ col += 2;
+ else
+ col++;
+ }
+ return (off_t) col;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns the current offset of the cursor from the beginning of a file */
+
+off_t
+edit_get_cursor_offset (const WEdit * edit)
+{
+ return edit->buffer.curs1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns the current column position of the cursor */
+
+long
+edit_get_col (const WEdit * edit)
+{
+ return (long) edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
+ edit->buffer.curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Scrolling functions */
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_update_curs_row (WEdit * edit)
+{
+ edit->curs_row = edit->buffer.curs_line - edit->start_line;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_update_curs_col (WEdit * edit)
+{
+ edit->curs_col = (long) edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer),
+ 0, edit->buffer.curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+long
+edit_get_curs_col (const WEdit * edit)
+{
+ return edit->curs_col;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** moves the display start position up by i lines */
+
+void
+edit_scroll_upward (WEdit * edit, long i)
+{
+ long lines_above = edit->start_line;
+
+ if (i > lines_above)
+ i = lines_above;
+ if (i != 0)
+ {
+ edit->start_line -= i;
+ edit->start_display =
+ edit_buffer_get_backward_offset (&edit->buffer, edit->start_display, i);
+ edit->force |= REDRAW_PAGE;
+ edit->force &= (0xfff - REDRAW_CHAR_ONLY);
+ }
+ edit_update_curs_row (edit);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_scroll_downward (WEdit * edit, long i)
+{
+ long lines_below;
+
+ lines_below = edit->buffer.lines - edit->start_line - (WIDGET (edit)->rect.lines - 1);
+ if (lines_below > 0)
+ {
+ if (i > lines_below)
+ i = lines_below;
+ edit->start_line += i;
+ edit->start_display =
+ edit_buffer_get_forward_offset (&edit->buffer, edit->start_display, i, 0);
+ edit->force |= REDRAW_PAGE;
+ edit->force &= (0xfff - REDRAW_CHAR_ONLY);
+ }
+ edit_update_curs_row (edit);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_scroll_right (WEdit * edit, long i)
+{
+ edit->force |= REDRAW_PAGE;
+ edit->force &= (0xfff - REDRAW_CHAR_ONLY);
+ edit->start_col -= i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_scroll_left (WEdit * edit, long i)
+{
+ if (edit->start_col)
+ {
+ edit->start_col += i;
+ if (edit->start_col > 0)
+ edit->start_col = 0;
+ edit->force |= REDRAW_PAGE;
+ edit->force &= (0xfff - REDRAW_CHAR_ONLY);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* high level cursor movement commands */
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_move_to_prev_col (WEdit * edit, off_t p)
+{
+ long prev = edit->prev_col;
+ long over = edit->over_col;
+
+ edit_cursor_move (edit,
+ edit_move_forward3 (edit, p, prev + edit->over_col, 0) - edit->buffer.curs1);
+
+ if (edit_options.cursor_beyond_eol)
+ {
+ long line_len;
+
+ line_len = (long) edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
+ edit_buffer_get_current_eol (&edit->buffer));
+ if (line_len < prev + edit->over_col)
+ {
+ edit->over_col = prev + over - line_len;
+ edit->prev_col = line_len;
+ edit->curs_col = line_len;
+ }
+ else
+ {
+ edit->curs_col = prev + over;
+ edit->prev_col = edit->curs_col;
+ edit->over_col = 0;
+ }
+ }
+ else
+ {
+ edit->over_col = 0;
+ if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer))
+ {
+ long fake_half_tabs;
+
+ edit_update_curs_col (edit);
+
+ fake_half_tabs = HALF_TAB_SIZE * space_width;
+ if (fake_half_tabs != 0 && edit->curs_col % fake_half_tabs != 0)
+ {
+ long q;
+
+ q = edit->curs_col;
+ edit->curs_col -= (edit->curs_col % fake_half_tabs);
+ p = edit_buffer_get_current_bol (&edit->buffer);
+ edit_cursor_move (edit,
+ edit_move_forward3 (edit, p, edit->curs_col,
+ 0) - edit->buffer.curs1);
+ if (!left_of_four_spaces (edit))
+ edit_cursor_move (edit,
+ edit_move_forward3 (edit, p, q, 0) - edit->buffer.curs1);
+ }
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** check whether line in editor is blank or not
+ *
+ * @param edit editor object
+ * @param line number of line
+ *
+ * @return TRUE if line in blank, FALSE otherwise
+ */
+
+gboolean
+edit_line_is_blank (WEdit * edit, long line)
+{
+ return is_blank (&edit->buffer, edit_find_line (edit, line));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** move cursor to line 'line' */
+
+void
+edit_move_to_line (WEdit * e, long line)
+{
+ if (line < e->buffer.curs_line)
+ edit_move_up (e, e->buffer.curs_line - line, FALSE);
+ else
+ edit_move_down (e, line - e->buffer.curs_line, FALSE);
+ edit_scroll_screen_over_cursor (e);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** scroll window so that first visible line is 'line' */
+
+void
+edit_move_display (WEdit * e, long line)
+{
+ if (line < e->start_line)
+ edit_scroll_upward (e, e->start_line - line);
+ else
+ edit_scroll_downward (e, line - e->start_line);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** save markers onto undo stack */
+
+void
+edit_push_markers (WEdit * edit)
+{
+ edit_push_undo_action (edit, MARK_1 + edit->mark1);
+ edit_push_undo_action (edit, MARK_2 + edit->mark2);
+ edit_push_undo_action (edit, MARK_CURS + edit->end_mark_curs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_set_markers (WEdit * edit, off_t m1, off_t m2, long c1, long c2)
+{
+ edit->mark1 = m1;
+ edit->mark2 = m2;
+ edit->column1 = c1;
+ edit->column2 = c2;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/** highlight marker toggle */
+
+void
+edit_mark_cmd (WEdit * edit, gboolean unmark)
+{
+ edit_push_markers (edit);
+ if (unmark)
+ {
+ edit_set_markers (edit, 0, 0, 0, 0);
+ edit->force |= REDRAW_PAGE;
+ }
+ else if (edit->mark2 >= 0)
+ {
+ edit->end_mark_curs = -1;
+ edit_set_markers (edit, edit->buffer.curs1, -1, edit->curs_col + edit->over_col,
+ edit->curs_col + edit->over_col);
+ edit->force |= REDRAW_PAGE;
+ }
+ else
+ {
+ edit->end_mark_curs = edit->buffer.curs1;
+ edit_set_markers (edit, edit->mark1, edit->buffer.curs1, edit->column1,
+ edit->curs_col + edit->over_col);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** highlight the word under cursor */
+
+void
+edit_mark_current_word_cmd (WEdit * edit)
+{
+ long pos;
+
+ for (pos = edit->buffer.curs1; pos != 0; pos--)
+ {
+ int c1, c2;
+
+ c1 = edit_buffer_get_byte (&edit->buffer, pos);
+ c2 = edit_buffer_get_byte (&edit->buffer, pos - 1);
+ if (!isspace (c1) && isspace (c2))
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ }
+ edit->mark1 = pos;
+
+ for (; pos < edit->buffer.size; pos++)
+ {
+ int c1, c2;
+
+ c1 = edit_buffer_get_byte (&edit->buffer, pos);
+ c2 = edit_buffer_get_byte (&edit->buffer, pos + 1);
+ if (!isspace (c1) && isspace (c2))
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ }
+ edit->mark2 = MIN (pos + 1, edit->buffer.size);
+
+ edit->force |= REDRAW_LINE_ABOVE | REDRAW_AFTER_CURSOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_mark_current_line_cmd (WEdit * edit)
+{
+ edit->mark1 = edit_buffer_get_current_bol (&edit->buffer);
+ edit->mark2 = edit_buffer_get_current_eol (&edit->buffer);
+
+ edit->force |= REDRAW_LINE_ABOVE | REDRAW_AFTER_CURSOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_delete_line (WEdit * edit)
+{
+ /*
+ * Delete right part of the line.
+ * Note that edit_buffer_get_byte() returns '\n' when byte position is
+ * beyond EOF.
+ */
+ while (edit_buffer_get_current_byte (&edit->buffer) != '\n')
+ (void) edit_delete (edit, TRUE);
+
+ /*
+ * Delete '\n' char.
+ * Note that edit_delete() will not corrupt anything if called while
+ * cursor position is EOF.
+ */
+ (void) edit_delete (edit, TRUE);
+
+ /*
+ * Delete left part of the line.
+ * Note, that edit_buffer_get_byte() returns '\n' when byte position is < 0.
+ */
+ while (edit_buffer_get_previous_byte (&edit->buffer) != '\n')
+ (void) edit_backspace (edit, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_push_key_press (WEdit * edit)
+{
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+ if (edit->mark2 == -1)
+ {
+ edit_push_undo_action (edit, MARK_1 + edit->mark1);
+ edit_push_undo_action (edit, MARK_CURS + edit->end_mark_curs);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_find_bracket (WEdit * edit)
+{
+ edit->bracket = edit_get_bracket (edit, 1, 10000);
+ if (edit->last_bracket != edit->bracket)
+ edit->force |= REDRAW_PAGE;
+ edit->last_bracket = edit->bracket;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * This executes a command as though the user initiated it through a key
+ * press. Callback with MSG_KEY as a message calls this after
+ * translating the key press. This function can be used to pass any
+ * command to the editor. Note that the screen wouldn't update
+ * automatically. Either of command or char_for_insertion must be
+ * passed as -1. Commands are executed, and char_for_insertion is
+ * inserted at the cursor.
+ */
+
+void
+edit_execute_key_command (WEdit * edit, long command, int char_for_insertion)
+{
+ if (command == CK_MacroStartRecord || command == CK_RepeatStartRecord
+ || (macro_index < 0
+ && (command == CK_MacroStartStopRecord || command == CK_RepeatStartStopRecord)))
+ {
+ macro_index = 0;
+ edit->force |= REDRAW_CHAR_ONLY | REDRAW_LINE;
+ return;
+ }
+ if (macro_index != -1)
+ {
+ edit->force |= REDRAW_COMPLETELY;
+ if (command == CK_MacroStopRecord || command == CK_MacroStartStopRecord)
+ {
+ edit_store_macro_cmd (edit);
+ macro_index = -1;
+ return;
+ }
+ if (command == CK_RepeatStopRecord || command == CK_RepeatStartStopRecord)
+ {
+ edit_repeat_macro_cmd (edit);
+ macro_index = -1;
+ return;
+ }
+ }
+
+ if (macro_index >= 0 && macro_index < MAX_MACRO_LENGTH - 1)
+ {
+ record_macro_buf[macro_index].action = command;
+ record_macro_buf[macro_index++].ch = char_for_insertion;
+ }
+ /* record the beginning of a set of editing actions initiated by a key press */
+ if (command != CK_Undo && command != CK_ExtendedKeyMap)
+ edit_push_key_press (edit);
+
+ edit_execute_cmd (edit, command, char_for_insertion);
+ if (edit->column_highlight)
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ This executes a command at a lower level than macro recording.
+ It also does not push a key_press onto the undo stack. This means
+ that if it is called many times, a single undo command will undo
+ all of them. It also does not check for the Undo command.
+ */
+void
+edit_execute_cmd (WEdit * edit, long command, int char_for_insertion)
+{
+ WRect *w = &WIDGET (edit)->rect;
+
+ if (command == CK_WindowFullscreen)
+ {
+ edit_toggle_fullscreen (edit);
+ return;
+ }
+
+ /* handle window state */
+ if (edit_handle_move_resize (edit, command))
+ return;
+
+ edit->force |= REDRAW_LINE;
+
+ /* The next key press will unhighlight the found string, so update
+ * the whole page */
+ if (edit->found_len || edit->column_highlight)
+ edit->force |= REDRAW_PAGE;
+
+ switch (command)
+ {
+ /* a mark command with shift-arrow */
+ case CK_MarkLeft:
+ case CK_MarkRight:
+ case CK_MarkToWordBegin:
+ case CK_MarkToWordEnd:
+ case CK_MarkToHome:
+ case CK_MarkToEnd:
+ case CK_MarkUp:
+ case CK_MarkDown:
+ case CK_MarkPageUp:
+ case CK_MarkPageDown:
+ case CK_MarkToFileBegin:
+ case CK_MarkToFileEnd:
+ case CK_MarkToPageBegin:
+ case CK_MarkToPageEnd:
+ case CK_MarkScrollUp:
+ case CK_MarkScrollDown:
+ case CK_MarkParagraphUp:
+ case CK_MarkParagraphDown:
+ /* a mark command with alt-arrow */
+ case CK_MarkColumnPageUp:
+ case CK_MarkColumnPageDown:
+ case CK_MarkColumnLeft:
+ case CK_MarkColumnRight:
+ case CK_MarkColumnUp:
+ case CK_MarkColumnDown:
+ case CK_MarkColumnScrollUp:
+ case CK_MarkColumnScrollDown:
+ case CK_MarkColumnParagraphUp:
+ case CK_MarkColumnParagraphDown:
+ edit->column_highlight = 0;
+ if (edit->highlight == 0 || (edit->mark2 != -1 && edit->mark1 != edit->mark2))
+ {
+ edit_mark_cmd (edit, TRUE); /* clear */
+ edit_mark_cmd (edit, FALSE); /* marking on */
+ }
+ edit->highlight = 1;
+ break;
+
+ /* any other command */
+ default:
+ if (edit->highlight)
+ edit_mark_cmd (edit, FALSE); /* clear */
+ edit->highlight = 0;
+ }
+
+ /* first check for undo */
+ if (command == CK_Undo)
+ {
+ edit->redo_stack_reset = 0;
+ edit_group_undo (edit);
+ edit->found_len = 0;
+ edit->prev_col = edit_get_col (edit);
+ edit->search_start = edit->buffer.curs1;
+ return;
+ }
+ /* check for redo */
+ if (command == CK_Redo)
+ {
+ edit->redo_stack_reset = 0;
+ edit_do_redo (edit);
+ edit->found_len = 0;
+ edit->prev_col = edit_get_col (edit);
+ edit->search_start = edit->buffer.curs1;
+ return;
+ }
+
+ edit->redo_stack_reset = 1;
+
+ /* An ordinary key press */
+ if (char_for_insertion >= 0)
+ {
+ /* if non persistent selection and text selected */
+ if (!edit_options.persistent_selections && edit->mark1 != edit->mark2)
+ edit_block_delete_cmd (edit);
+
+ if (edit->overwrite)
+ {
+ /* remove char only one time, after input first byte, multibyte chars */
+#ifdef HAVE_CHARSET
+ if (!mc_global.utf8_display || edit->charpoint == 0)
+#endif
+ if (edit_buffer_get_current_byte (&edit->buffer) != '\n')
+
+ edit_delete (edit, FALSE);
+ }
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+#ifdef HAVE_CHARSET
+ /**
+ Encode 8-bit input as UTF-8, if display (locale) is *not* UTF-8,
+ *but* source encoding *is* set to UTF-8; see ticket #3843 for the details.
+ */
+ if (char_for_insertion > 127 && str_isutf8 (get_codepage_id (mc_global.source_codepage))
+ && !mc_global.utf8_display)
+ {
+ unsigned char str[UTF8_CHAR_LEN + 1];
+ size_t i = 0;
+ int res;
+
+ res = g_unichar_to_utf8 (char_for_insertion, (char *) str);
+ if (res == 0)
+ {
+ str[0] = '.';
+ str[1] = '\0';
+ }
+ else
+ {
+ str[res] = '\0';
+ }
+ while (i <= UTF8_CHAR_LEN && str[i] != '\0')
+ {
+ char_for_insertion = str[i];
+ edit_insert (edit, char_for_insertion);
+ i++;
+ }
+ }
+ else
+#endif
+ edit_insert (edit, char_for_insertion);
+
+ if (edit_options.auto_para_formatting)
+ {
+ format_paragraph (edit, FALSE);
+ edit->force |= REDRAW_PAGE;
+ }
+ else
+ check_and_wrap_line (edit);
+ edit->found_len = 0;
+ edit->prev_col = edit_get_col (edit);
+ edit->search_start = edit->buffer.curs1;
+ edit_find_bracket (edit);
+ return;
+ }
+
+ switch (command)
+ {
+ case CK_TopOnScreen:
+ case CK_BottomOnScreen:
+ case CK_Top:
+ case CK_Bottom:
+ case CK_PageUp:
+ case CK_PageDown:
+ case CK_Home:
+ case CK_End:
+ case CK_Up:
+ case CK_Down:
+ case CK_Left:
+ case CK_Right:
+ case CK_WordLeft:
+ case CK_WordRight:
+ if (!edit_options.persistent_selections && edit->mark2 >= 0)
+ {
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ edit_mark_cmd (edit, TRUE);
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (command)
+ {
+ case CK_TopOnScreen:
+ case CK_BottomOnScreen:
+ case CK_MarkToPageBegin:
+ case CK_MarkToPageEnd:
+ case CK_Up:
+ case CK_Down:
+ case CK_WordLeft:
+ case CK_WordRight:
+ case CK_MarkToWordBegin:
+ case CK_MarkToWordEnd:
+ case CK_MarkUp:
+ case CK_MarkDown:
+ case CK_MarkColumnUp:
+ case CK_MarkColumnDown:
+ if (edit->mark2 == -1)
+ break; /*marking is following the cursor: may need to highlight a whole line */
+ MC_FALLTHROUGH;
+ case CK_Left:
+ case CK_Right:
+ case CK_MarkLeft:
+ case CK_MarkRight:
+ edit->force |= REDRAW_CHAR_ONLY;
+ break;
+ default:
+ break;
+ }
+
+ /* basic cursor key commands */
+ switch (command)
+ {
+ case CK_BackSpace:
+ /* if non persistent selection and text selected */
+ if (!edit_options.persistent_selections && edit->mark1 != edit->mark2)
+ edit_block_delete_cmd (edit);
+ else if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit->over_col--;
+ else if (edit_options.backspace_through_tabs && is_in_indent (&edit->buffer))
+ {
+ while (edit_buffer_get_previous_byte (&edit->buffer) != '\n' && edit->buffer.curs1 > 0)
+ edit_backspace (edit, TRUE);
+ }
+ else if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer)
+ && right_of_four_spaces (edit))
+ {
+ int i;
+
+ for (i = 0; i < HALF_TAB_SIZE; i++)
+ edit_backspace (edit, TRUE);
+ }
+ else
+ edit_backspace (edit, FALSE);
+ break;
+ case CK_Delete:
+ /* if non persistent selection and text selected */
+ if (!edit_options.persistent_selections && edit->mark1 != edit->mark2)
+ edit_block_delete_cmd (edit);
+ else
+ {
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+
+ if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer)
+ && left_of_four_spaces (edit))
+ {
+ int i;
+
+ for (i = 1; i <= HALF_TAB_SIZE; i++)
+ edit_delete (edit, TRUE);
+ }
+ else
+ edit_delete (edit, FALSE);
+ }
+ break;
+ case CK_DeleteToWordBegin:
+ edit->over_col = 0;
+ edit_left_delete_word (edit);
+ break;
+ case CK_DeleteToWordEnd:
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+
+ edit_right_delete_word (edit);
+ break;
+ case CK_DeleteLine:
+ edit_delete_line (edit);
+ break;
+ case CK_DeleteToHome:
+ edit_delete_to_line_begin (edit);
+ break;
+ case CK_DeleteToEnd:
+ edit_delete_to_line_end (edit);
+ break;
+ case CK_Enter:
+ edit->over_col = 0;
+ if (edit_options.auto_para_formatting)
+ {
+ edit_double_newline (edit);
+ if (edit_options.return_does_auto_indent && !bracketed_pasting_in_progress)
+ edit_auto_indent (edit);
+ format_paragraph (edit, FALSE);
+ }
+ else
+ {
+ edit_insert (edit, '\n');
+ if (edit_options.return_does_auto_indent && !bracketed_pasting_in_progress)
+ edit_auto_indent (edit);
+ }
+ break;
+ case CK_Return:
+ edit_insert (edit, '\n');
+ break;
+
+ case CK_MarkColumnPageUp:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_PageUp:
+ case CK_MarkPageUp:
+ edit_move_up (edit, w->lines - 1, TRUE);
+ break;
+ case CK_MarkColumnPageDown:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_PageDown:
+ case CK_MarkPageDown:
+ edit_move_down (edit, w->lines - 1, TRUE);
+ break;
+ case CK_MarkColumnLeft:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_Left:
+ case CK_MarkLeft:
+ if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer)
+ && right_of_four_spaces (edit))
+ {
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit->over_col--;
+ else
+ edit_cursor_move (edit, -HALF_TAB_SIZE);
+ edit->force &= (0xFFF - REDRAW_CHAR_ONLY);
+ }
+ else
+ edit_left_char_move_cmd (edit);
+ break;
+ case CK_MarkColumnRight:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_Right:
+ case CK_MarkRight:
+ if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer)
+ && left_of_four_spaces (edit))
+ {
+ edit_cursor_move (edit, HALF_TAB_SIZE);
+ edit->force &= (0xFFF - REDRAW_CHAR_ONLY);
+ }
+ else
+ edit_right_char_move_cmd (edit);
+ break;
+ case CK_TopOnScreen:
+ case CK_MarkToPageBegin:
+ edit_begin_page (edit);
+ break;
+ case CK_BottomOnScreen:
+ case CK_MarkToPageEnd:
+ edit_end_page (edit);
+ break;
+ case CK_WordLeft:
+ case CK_MarkToWordBegin:
+ edit->over_col = 0;
+ edit_left_word_move_cmd (edit);
+ break;
+ case CK_WordRight:
+ case CK_MarkToWordEnd:
+ edit->over_col = 0;
+ edit_right_word_move_cmd (edit);
+ break;
+ case CK_MarkColumnUp:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_Up:
+ case CK_MarkUp:
+ edit_move_up (edit, 1, FALSE);
+ break;
+ case CK_MarkColumnDown:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_Down:
+ case CK_MarkDown:
+ edit_move_down (edit, 1, FALSE);
+ break;
+ case CK_MarkColumnParagraphUp:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_ParagraphUp:
+ case CK_MarkParagraphUp:
+ edit_move_up_paragraph (edit, FALSE);
+ break;
+ case CK_MarkColumnParagraphDown:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_ParagraphDown:
+ case CK_MarkParagraphDown:
+ edit_move_down_paragraph (edit, FALSE);
+ break;
+ case CK_MarkColumnScrollUp:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_ScrollUp:
+ case CK_MarkScrollUp:
+ edit_move_up (edit, 1, TRUE);
+ break;
+ case CK_MarkColumnScrollDown:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_ScrollDown:
+ case CK_MarkScrollDown:
+ edit_move_down (edit, 1, TRUE);
+ break;
+ case CK_Home:
+ case CK_MarkToHome:
+ edit_cursor_to_bol (edit);
+ break;
+ case CK_End:
+ case CK_MarkToEnd:
+ edit_cursor_to_eol (edit);
+ break;
+ case CK_Tab:
+ /* if text marked shift block */
+ if (edit->mark1 != edit->mark2 && !edit_options.persistent_selections)
+ {
+ if (edit->mark2 < 0)
+ edit_mark_cmd (edit, FALSE);
+ edit_move_block_to_right (edit);
+ }
+ else
+ {
+ if (edit_options.cursor_beyond_eol)
+ edit_insert_over (edit);
+ edit_tab_cmd (edit);
+ if (edit_options.auto_para_formatting)
+ {
+ format_paragraph (edit, FALSE);
+ edit->force |= REDRAW_PAGE;
+ }
+ else
+ check_and_wrap_line (edit);
+ }
+ break;
+
+ case CK_InsertOverwrite:
+ edit->overwrite = !edit->overwrite;
+ break;
+
+ case CK_Mark:
+ if (edit->mark2 >= 0)
+ {
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ }
+ edit_mark_cmd (edit, FALSE);
+ break;
+ case CK_MarkColumn:
+ if (!edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_OFF);
+ edit->column_highlight = 1;
+ edit_mark_cmd (edit, FALSE);
+ break;
+ case CK_MarkAll:
+ edit_set_markers (edit, 0, edit->buffer.size, 0, 0);
+ edit->force |= REDRAW_PAGE;
+ break;
+ case CK_Unmark:
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ edit_mark_cmd (edit, TRUE);
+ break;
+ case CK_MarkWord:
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ edit_mark_current_word_cmd (edit);
+ break;
+ case CK_MarkLine:
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ edit_mark_current_line_cmd (edit);
+ break;
+
+ case CK_Bookmark:
+ book_mark_clear (edit, edit->buffer.curs_line, BOOK_MARK_FOUND_COLOR);
+ if (book_mark_query_color (edit, edit->buffer.curs_line, BOOK_MARK_COLOR))
+ book_mark_clear (edit, edit->buffer.curs_line, BOOK_MARK_COLOR);
+ else
+ book_mark_insert (edit, edit->buffer.curs_line, BOOK_MARK_COLOR);
+ break;
+ case CK_BookmarkFlush:
+ book_mark_flush (edit, BOOK_MARK_COLOR);
+ book_mark_flush (edit, BOOK_MARK_FOUND_COLOR);
+ edit->force |= REDRAW_PAGE;
+ break;
+ case CK_BookmarkNext:
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ p = book_mark_find (edit, edit->buffer.curs_line);
+ if (p->next != NULL)
+ {
+ p = p->next;
+ if (p->line >= edit->start_line + w->lines || p->line < edit->start_line)
+ edit_move_display (edit, p->line - w->lines / 2);
+ edit_move_to_line (edit, p->line);
+ }
+ }
+ break;
+ case CK_BookmarkPrev:
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ p = book_mark_find (edit, edit->buffer.curs_line);
+ while (p->line == edit->buffer.curs_line)
+ if (p->prev != NULL)
+ p = p->prev;
+ if (p->line >= 0)
+ {
+ if (p->line >= edit->start_line + w->lines || p->line < edit->start_line)
+ edit_move_display (edit, p->line - w->lines / 2);
+ edit_move_to_line (edit, p->line);
+ }
+ }
+ break;
+
+ case CK_Top:
+ case CK_MarkToFileBegin:
+ edit_move_to_top (edit);
+ break;
+ case CK_Bottom:
+ case CK_MarkToFileEnd:
+ edit_move_to_bottom (edit);
+ break;
+
+ case CK_Copy:
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+ edit_block_copy_cmd (edit);
+ break;
+ case CK_Remove:
+ edit_block_delete_cmd (edit);
+ break;
+ case CK_Move:
+ edit_block_move_cmd (edit);
+ break;
+
+ case CK_BlockShiftLeft:
+ if (edit->mark1 != edit->mark2)
+ edit_move_block_to_left (edit);
+ break;
+ case CK_BlockShiftRight:
+ if (edit->mark1 != edit->mark2)
+ edit_move_block_to_right (edit);
+ break;
+ case CK_Store:
+ edit_copy_to_X_buf_cmd (edit);
+ break;
+ case CK_Cut:
+ edit_cut_to_X_buf_cmd (edit);
+ break;
+ case CK_Paste:
+ /* if non persistent selection and text selected */
+ if (!edit_options.persistent_selections && edit->mark1 != edit->mark2)
+ edit_block_delete_cmd (edit);
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+ edit_paste_from_X_buf_cmd (edit);
+ if (!edit_options.persistent_selections && edit->mark2 >= 0)
+ {
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ edit_mark_cmd (edit, TRUE);
+ }
+ break;
+ case CK_History:
+ edit_paste_from_history (edit);
+ break;
+
+ case CK_SaveAs:
+ edit_save_as_cmd (edit);
+ break;
+ case CK_Save:
+ edit_save_confirm_cmd (edit);
+ break;
+ case CK_BlockSave:
+ edit_save_block_cmd (edit);
+ break;
+ case CK_InsertFile:
+ edit_insert_file_cmd (edit);
+ break;
+
+ case CK_FilePrev:
+ edit_load_back_cmd (edit);
+ break;
+ case CK_FileNext:
+ edit_load_forward_cmd (edit);
+ break;
+
+ case CK_SyntaxChoose:
+ edit_syntax_dialog (edit);
+ break;
+
+ case CK_Search:
+ edit_search_cmd (edit, FALSE);
+ break;
+ case CK_SearchContinue:
+ edit_search_cmd (edit, TRUE);
+ break;
+ case CK_Replace:
+ edit_replace_cmd (edit, FALSE);
+ break;
+ case CK_ReplaceContinue:
+ edit_replace_cmd (edit, TRUE);
+ break;
+ case CK_Complete:
+ /* if text marked shift block */
+ if (edit->mark1 != edit->mark2 && !edit_options.persistent_selections)
+ edit_move_block_to_left (edit);
+ else
+ edit_complete_word_cmd (edit);
+ break;
+ case CK_Find:
+ edit_get_match_keyword_cmd (edit);
+ break;
+
+#ifdef HAVE_ASPELL
+ case CK_SpellCheckCurrentWord:
+ edit_suggest_current_word (edit);
+ break;
+ case CK_SpellCheck:
+ edit_spellcheck_file (edit);
+ break;
+ case CK_SpellCheckSelectLang:
+ edit_set_spell_lang ();
+ break;
+#endif
+
+ case CK_Date:
+ {
+ char s[BUF_MEDIUM];
+ /* fool gcc to prevent a Y2K warning */
+ char time_format[] = "_c";
+ time_format[0] = '%';
+
+ FMT_LOCALTIME_CURRENT (s, sizeof (s), time_format);
+ edit_print_string (edit, s);
+ edit->force |= REDRAW_PAGE;
+ }
+ break;
+ case CK_Goto:
+ edit_goto_cmd (edit);
+ break;
+ case CK_ParagraphFormat:
+ format_paragraph (edit, TRUE);
+ edit->force |= REDRAW_PAGE;
+ break;
+ case CK_MacroDelete:
+ edit_delete_macro_cmd (edit);
+ break;
+ case CK_MatchBracket:
+ edit_goto_matching_bracket (edit);
+ break;
+ case CK_UserMenu:
+ user_menu (edit, NULL, -1);
+ break;
+ case CK_Sort:
+ edit_sort_cmd (edit);
+ break;
+ case CK_ExternalCommand:
+ edit_ext_cmd (edit);
+ break;
+ case CK_EditMail:
+ edit_mail_dialog (edit);
+ break;
+#ifdef HAVE_CHARSET
+ case CK_SelectCodepage:
+ edit_select_codepage_cmd (edit);
+ break;
+#endif
+ case CK_InsertLiteral:
+ edit_insert_literal_cmd (edit);
+ break;
+ case CK_MacroStartStopRecord:
+ edit_begin_end_macro_cmd (edit);
+ break;
+ case CK_RepeatStartStopRecord:
+ edit_begin_end_repeat_cmd (edit);
+ break;
+ case CK_ExtendedKeyMap:
+ WIDGET (edit)->ext_mode = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ /* CK_PipeBlock */
+ if ((command / CK_PipeBlock (0)) == 1)
+ edit_block_process_cmd (edit, command - CK_PipeBlock (0));
+
+ /* keys which must set the col position, and the search vars */
+ switch (command)
+ {
+ case CK_Search:
+ case CK_SearchContinue:
+ case CK_Replace:
+ case CK_ReplaceContinue:
+ case CK_Complete:
+ edit->prev_col = edit_get_col (edit);
+ break;
+ case CK_Up:
+ case CK_MarkUp:
+ case CK_MarkColumnUp:
+ case CK_Down:
+ case CK_MarkDown:
+ case CK_MarkColumnDown:
+ case CK_PageUp:
+ case CK_MarkPageUp:
+ case CK_MarkColumnPageUp:
+ case CK_PageDown:
+ case CK_MarkPageDown:
+ case CK_MarkColumnPageDown:
+ case CK_Top:
+ case CK_MarkToFileBegin:
+ case CK_Bottom:
+ case CK_MarkToFileEnd:
+ case CK_ParagraphUp:
+ case CK_MarkParagraphUp:
+ case CK_MarkColumnParagraphUp:
+ case CK_ParagraphDown:
+ case CK_MarkParagraphDown:
+ case CK_MarkColumnParagraphDown:
+ case CK_ScrollUp:
+ case CK_MarkScrollUp:
+ case CK_MarkColumnScrollUp:
+ case CK_ScrollDown:
+ case CK_MarkScrollDown:
+ case CK_MarkColumnScrollDown:
+ edit->search_start = edit->buffer.curs1;
+ edit->found_len = 0;
+ break;
+ default:
+ edit->found_len = 0;
+ edit->prev_col = edit_get_col (edit);
+ edit->search_start = edit->buffer.curs1;
+ }
+ edit_find_bracket (edit);
+
+ if (edit_options.auto_para_formatting)
+ {
+ switch (command)
+ {
+ case CK_BackSpace:
+ case CK_Delete:
+ case CK_DeleteToWordBegin:
+ case CK_DeleteToWordEnd:
+ case CK_DeleteToHome:
+ case CK_DeleteToEnd:
+ format_paragraph (edit, FALSE);
+ edit->force |= REDRAW_PAGE;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_stack_init (void)
+{
+ for (edit_stack_iterator = 0; edit_stack_iterator < MAX_HISTORY_MOVETO; edit_stack_iterator++)
+ {
+ edit_history_moveto[edit_stack_iterator].filename_vpath = NULL;
+ edit_history_moveto[edit_stack_iterator].line = -1;
+ }
+
+ edit_stack_iterator = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_stack_free (void)
+{
+ for (edit_stack_iterator = 0; edit_stack_iterator < MAX_HISTORY_MOVETO; edit_stack_iterator++)
+ vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** move i lines */
+
+void
+edit_move_up (WEdit * edit, long i, gboolean do_scroll)
+{
+ edit_move_updown (edit, i, do_scroll, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** move i lines */
+
+void
+edit_move_down (WEdit * edit, long i, gboolean do_scroll)
+{
+ edit_move_updown (edit, i, do_scroll, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/edit.h b/src/editor/edit.h
new file mode 100644
index 0000000..358aa3f
--- /dev/null
+++ b/src/editor/edit.h
@@ -0,0 +1,84 @@
+/*
+ Editor public API
+ */
+
+/** \file edit.h
+ * \brief Header: editor public API
+ * \author Paul Sheer
+ * \date 1996, 1997
+ * \author Andrew Borodin
+ * \date 2009, 2012
+ */
+
+#ifndef MC__EDIT_H
+#define MC__EDIT_H
+
+#include "lib/global.h" /* PATH_SEP_STR */
+#include "lib/vfs/vfs.h" /* vfs_path_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define DEFAULT_WRAP_LINE_LENGTH 72
+
+#define EDIT(x) ((WEdit *)(x))
+#define CONST_EDIT(x) ((const WEdit *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Editor widget */
+struct WEdit;
+typedef struct WEdit WEdit;
+
+typedef struct
+{
+ int word_wrap_line_length;
+ gboolean typewriter_wrap;
+ gboolean auto_para_formatting;
+ gboolean fill_tabs_with_spaces;
+ gboolean return_does_auto_indent;
+ gboolean backspace_through_tabs;
+ gboolean fake_half_tabs;
+ gboolean persistent_selections;
+ gboolean drop_selection_on_copy; /* whether we need to drop selection on copy to buffer */
+ gboolean cursor_beyond_eol;
+ gboolean cursor_after_inserted_block;
+ gboolean state_full_filename;
+ gboolean line_state;
+ int line_state_width;
+ int save_mode;
+ gboolean confirm_save; /* queries on a save */
+ gboolean save_position;
+ gboolean syntax_highlighting;
+ gboolean group_undo;
+ char *backup_ext;
+ char *filesize_threshold;
+ char *stop_format_chars;
+ gboolean visible_tabs;
+ gboolean visible_tws;
+ gboolean show_right_margin;
+ gboolean simple_statusbar; /* statusbar draw style */
+ gboolean check_nl_at_eof;
+} edit_options_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern edit_options_t edit_options;
+
+/*** declarations of public functions ************************************************************/
+
+/* used in main() */
+void edit_stack_init (void);
+void edit_stack_free (void);
+
+gboolean edit_file (const vfs_path_t * file_vpath, long line);
+gboolean edit_files (const GList * files);
+
+const char *edit_get_file_name (const WEdit * edit);
+off_t edit_get_cursor_offset (const WEdit * edit);
+long edit_get_curs_col (const WEdit * edit);
+const char *edit_get_syntax_type (const WEdit * edit);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__EDIT_H */
diff --git a/src/editor/editbuffer.c b/src/editor/editbuffer.c
new file mode 100644
index 0000000..24bc7ee
--- /dev/null
+++ b/src/editor/editbuffer.c
@@ -0,0 +1,900 @@
+/*
+ Editor text keep buffer.
+
+ Copyright (C) 2013-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru> 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: editor text keep buffer.
+ * \author Andrew Borodin
+ * \date 2013
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/vfs/vfs.h"
+
+#include "edit-impl.h"
+#include "editbuffer.h"
+
+/* --------------------------------------------------------------------------------------------- */
+/*-
+ *
+ * here's a quick sketch of the layout: (don't run this through indent.)
+ *
+ * |
+ * \0\0\0\0\0m e _ f i l e . \nf i n . \n|T h i s _ i s _ s o\0\0\0\0\0\0\0\0\0
+ * ______________________________________|______________________________________
+ * |
+ * ... | b2[2] | b2[1] | b2[0] | b1[0] | b1[1] | b1[2] | ...
+ * |-> |-> |-> |-> |-> |-> |
+ * |
+ * _<------------------------->|<----------------->_
+ * curs2 | curs1
+ * ^ | ^
+ * | ^|^ |
+ * cursor ||| cursor
+ * |||
+ * file end|||file beginning
+ * |
+ * |
+ *
+ * _
+ * This_is_some_file
+ * fin.
+ *
+ *
+ * This is called a "gap buffer".
+ * See also:
+ * http://en.wikipedia.org/wiki/Gap_buffer
+ * http://stackoverflow.com/questions/4199694/data-structure-for-text-editor
+ */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*
+ * The editor keeps data in two arrays of buffers.
+ * All buffers have the same size, which must be a power of 2.
+ */
+
+/* Configurable: log2 of the buffer size in bytes */
+#ifndef S_EDIT_BUF_SIZE
+#define S_EDIT_BUF_SIZE 16
+#endif
+
+/* Size of the buffer */
+#define EDIT_BUF_SIZE (((off_t) 1) << S_EDIT_BUF_SIZE)
+
+/* Buffer mask (used to find cursor position relative to the buffer) */
+#define M_EDIT_BUF_SIZE (EDIT_BUF_SIZE - 1)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get pointer to byte at specified index
+ *
+ * @param buf pointer to editor buffer
+ * @param byte_index byte index
+ *
+ * @return NULL if byte_index is negative or larger than file size; pointer to byte otherwise.
+ */
+static char *
+edit_buffer_get_byte_ptr (const edit_buffer_t * buf, off_t byte_index)
+{
+ void *b;
+
+ if (byte_index >= (buf->curs1 + buf->curs2) || byte_index < 0)
+ return NULL;
+
+ if (byte_index >= buf->curs1)
+ {
+ off_t p;
+
+ p = buf->curs1 + buf->curs2 - byte_index - 1;
+ b = g_ptr_array_index (buf->b2, p >> S_EDIT_BUF_SIZE);
+ return (char *) b + EDIT_BUF_SIZE - 1 - (p & M_EDIT_BUF_SIZE);
+ }
+
+ b = g_ptr_array_index (buf->b1, byte_index >> S_EDIT_BUF_SIZE);
+ return (char *) b + (byte_index & M_EDIT_BUF_SIZE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialize editor buffers.
+ *
+ * @param buf pointer to editor buffer
+ */
+
+void
+edit_buffer_init (edit_buffer_t * buf, off_t size)
+{
+ buf->b1 = g_ptr_array_new_full (32, g_free);
+ buf->b2 = g_ptr_array_new_full (32, g_free);
+
+ buf->curs1 = 0;
+ buf->curs2 = 0;
+
+ buf->size = size;
+ buf->lines = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Clean editor buffers.
+ *
+ * @param buf pointer to editor buffer
+ */
+
+void
+edit_buffer_clean (edit_buffer_t * buf)
+{
+ if (buf->b1 != NULL)
+ g_ptr_array_free (buf->b1, TRUE);
+
+ if (buf->b2 != NULL)
+ g_ptr_array_free (buf->b2, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get byte at specified index
+ *
+ * @param buf pointer to editor buffer
+ * @param byte_index byte index
+ *
+ * @return '\n' if byte_index is negative or larger than file size; byte at byte_index otherwise.
+ */
+
+int
+edit_buffer_get_byte (const edit_buffer_t * buf, off_t byte_index)
+{
+ char *p;
+
+ p = edit_buffer_get_byte_ptr (buf, byte_index);
+
+ return (p != NULL) ? *(unsigned char *) p : '\n';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+/**
+ * Get utf-8 symbol at specified index
+ *
+ * @param buf pointer to editor buffer
+ * @param byte_index byte index
+ * @param char_length length of returned symbol
+ *
+ * @return '\n' if byte_index is negative or larger than file size;
+ * 0 if utf-8 symbol at specified index is invalid;
+ * utf-8 symbol otherwise
+ */
+
+int
+edit_buffer_get_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length)
+{
+ gchar *str = NULL;
+ gunichar res;
+ gunichar ch;
+ gchar *next_ch = NULL;
+
+ if (byte_index >= (buf->curs1 + buf->curs2) || byte_index < 0)
+ {
+ *char_length = 0;
+ return '\n';
+ }
+
+ str = edit_buffer_get_byte_ptr (buf, byte_index);
+ if (str == NULL)
+ {
+ *char_length = 0;
+ return 0;
+ }
+
+ res = g_utf8_get_char_validated (str, -1);
+ if (res == (gunichar) (-2) || res == (gunichar) (-1))
+ {
+ /* Retry with explicit bytes to make sure it's not a buffer boundary */
+ size_t i;
+ gchar utf8_buf[UTF8_CHAR_LEN + 1];
+
+ for (i = 0; i < UTF8_CHAR_LEN; i++)
+ utf8_buf[i] = edit_buffer_get_byte (buf, byte_index + i);
+ utf8_buf[i] = '\0';
+ res = g_utf8_get_char_validated (utf8_buf, -1);
+ }
+
+ if (res == (gunichar) (-2) || res == (gunichar) (-1))
+ {
+ ch = *str;
+ *char_length = 0;
+ }
+ else
+ {
+ ch = res;
+ /* Calculate UTF-8 char length */
+ next_ch = g_utf8_next_char (str);
+ *char_length = next_ch - str;
+ }
+
+ return (int) ch;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get utf-8 symbol before specified index
+ *
+ * @param buf pointer to editor buffer
+ * @param byte_index byte index
+ * @param char_length length of returned symbol
+ *
+ * @return 0 if byte_index is negative or larger than file size;
+ * 1-byte value before specified index if utf-8 symbol before specified index is invalid;
+ * utf-8 symbol otherwise
+ */
+
+int
+edit_buffer_get_prev_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length)
+{
+ size_t i;
+ gchar utf8_buf[3 * UTF8_CHAR_LEN + 1];
+ gchar *str;
+ gchar *cursor_buf_ptr;
+ gunichar res;
+
+ if (byte_index > (buf->curs1 + buf->curs2) || byte_index <= 0)
+ {
+ *char_length = 0;
+ return 0;
+ }
+
+ for (i = 0; i < (3 * UTF8_CHAR_LEN); i++)
+ utf8_buf[i] = edit_buffer_get_byte (buf, byte_index + i - (2 * UTF8_CHAR_LEN));
+ utf8_buf[i] = '\0';
+
+ cursor_buf_ptr = utf8_buf + (2 * UTF8_CHAR_LEN);
+ str = g_utf8_find_prev_char (utf8_buf, cursor_buf_ptr);
+
+ if (str == NULL || g_utf8_next_char (str) != cursor_buf_ptr)
+ {
+ *char_length = 1;
+ return *(cursor_buf_ptr - 1);
+ }
+
+ res = g_utf8_get_char_validated (str, -1);
+ if (res == (gunichar) (-2) || res == (gunichar) (-1))
+ {
+ *char_length = 1;
+ return *(cursor_buf_ptr - 1);
+ }
+
+ *char_length = cursor_buf_ptr - str;
+ return (int) res;
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Count lines in editor buffer.
+ *
+ * @param buf editor buffer
+ * @param first start byte offset
+ * @param last finish byte offset
+ *
+ * @return line numbers between "first" and "last" bytes
+ */
+
+long
+edit_buffer_count_lines (const edit_buffer_t * buf, off_t first, off_t last)
+{
+ long lines = 0;
+
+ first = MAX (first, 0);
+ last = MIN (last, buf->size);
+
+ while (first < last)
+ if (edit_buffer_get_byte (buf, first++) == '\n')
+ lines++;
+
+ return lines;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get "begin-of-line" offset of line contained specified byte offset
+ *
+ * @param buf editor buffer
+ * @param current byte offset
+ *
+ * @return index of first char of line
+ */
+
+off_t
+edit_buffer_get_bol (const edit_buffer_t * buf, off_t current)
+{
+ if (current <= 0)
+ return 0;
+
+ for (; edit_buffer_get_byte (buf, current - 1) != '\n'; current--)
+ ;
+
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get "end-of-line" offset of line contained specified byte offset
+ *
+ * @param buf editor buffer
+ * @param current byte offset
+ *
+ * @return index of last char of line + 1
+ */
+
+off_t
+edit_buffer_get_eol (const edit_buffer_t * buf, off_t current)
+{
+ if (current >= buf->size)
+ return buf->size;
+
+ for (; edit_buffer_get_byte (buf, current) != '\n'; current++)
+ ;
+
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get word from specified offset.
+ *
+ * @param buf editor buffer
+ * @param current start_pos offset
+ * @param start actual start word ofset
+ * @param cut
+ *
+ * @return word as newly allocated object
+ */
+
+GString *
+edit_buffer_get_word_from_pos (const edit_buffer_t * buf, off_t start_pos, off_t * start,
+ gsize * cut)
+{
+ off_t word_start;
+ gsize cut_len = 0;
+ GString *match_expr;
+ int c1, c2;
+
+ for (word_start = start_pos; word_start != 0; word_start--, cut_len++)
+ {
+ c1 = edit_buffer_get_byte (buf, word_start);
+ c2 = edit_buffer_get_byte (buf, word_start - 1);
+
+ if (is_break_char (c1) != is_break_char (c2) || c1 == '\n' || c2 == '\n')
+ break;
+ }
+
+ match_expr = g_string_sized_new (16);
+
+ do
+ {
+ c1 = edit_buffer_get_byte (buf, word_start + match_expr->len);
+ c2 = edit_buffer_get_byte (buf, word_start + match_expr->len + 1);
+ g_string_append_c (match_expr, c1);
+ }
+ while (!(is_break_char (c1) != is_break_char (c2) || c1 == '\n' || c2 == '\n'));
+
+ *start = word_start;
+ *cut = cut_len;
+
+ return match_expr;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find first character of current word
+ *
+ * @param buf editor buffer
+ * @param word_start position of first character of current word
+ * @param word_len length of current word
+ *
+ * @return TRUE if first character of word is found and this character is not 1) a digit and
+ * 2) a begin of file, FALSE otherwise
+ */
+
+gboolean
+edit_buffer_find_word_start (const edit_buffer_t * buf, off_t * word_start, gsize * word_len)
+{
+ int c;
+ off_t i;
+
+ /* return if at begin of file */
+ if (buf->curs1 <= 0)
+ return FALSE;
+
+ c = edit_buffer_get_previous_byte (buf);
+ /* return if not at end or in word */
+ if (is_break_char (c))
+ return FALSE;
+
+ /* search start of word */
+ for (i = 1;; i++)
+ {
+ int last;
+
+ last = c;
+ c = edit_buffer_get_byte (buf, buf->curs1 - i - 1);
+
+ if (is_break_char (c))
+ {
+ /* return if word starts with digit */
+ if (isdigit (last))
+ return FALSE;
+
+ break;
+ }
+ }
+
+ /* success */
+ *word_start = buf->curs1 - i; /* start found */
+ *word_len = (gsize) i;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Basic low level single character buffer alterations and movements at the cursor: insert character
+ * at the cursor position and move right.
+ *
+ * @param buf pointer to editor buffer
+ * @param c character to insert
+ */
+
+void
+edit_buffer_insert (edit_buffer_t * buf, int c)
+{
+ void *b;
+ off_t i;
+
+ i = buf->curs1 & M_EDIT_BUF_SIZE;
+
+ /* add a new buffer if we've reached the end of the last one */
+ if (i == 0)
+ g_ptr_array_add (buf->b1, g_malloc0 (EDIT_BUF_SIZE));
+
+ /* perform the insertion */
+ b = g_ptr_array_index (buf->b1, buf->curs1 >> S_EDIT_BUF_SIZE);
+ *((unsigned char *) b + i) = (unsigned char) c;
+
+ /* update cursor position */
+ buf->curs1++;
+
+ /* update file length */
+ buf->size++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Basic low level single character buffer alterations and movements at the cursor: insert character
+ * at the cursor position and move left.
+ *
+ * @param buf pointer to editor buffer
+ * @param c character to insert
+ */
+
+void
+edit_buffer_insert_ahead (edit_buffer_t * buf, int c)
+{
+ void *b;
+ off_t i;
+
+ i = buf->curs2 & M_EDIT_BUF_SIZE;
+
+ /* add a new buffer if we've reached the end of the last one */
+ if (i == 0)
+ g_ptr_array_add (buf->b2, g_malloc0 (EDIT_BUF_SIZE));
+
+ /* perform the insertion */
+ b = g_ptr_array_index (buf->b2, buf->curs2 >> S_EDIT_BUF_SIZE);
+ *((unsigned char *) b + EDIT_BUF_SIZE - 1 - i) = (unsigned char) c;
+
+ /* update cursor position */
+ buf->curs2++;
+
+ /* update file length */
+ buf->size++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Basic low level single character buffer alterations and movements at the cursor: delete character
+ * at the cursor position.
+ *
+ * @param buf pointer to editor buffer
+ * @param c character to insert
+ */
+
+int
+edit_buffer_delete (edit_buffer_t * buf)
+{
+ void *b;
+ unsigned char c;
+ off_t prev;
+ off_t i;
+
+ prev = buf->curs2 - 1;
+
+ b = g_ptr_array_index (buf->b2, prev >> S_EDIT_BUF_SIZE);
+ i = prev & M_EDIT_BUF_SIZE;
+ c = *((unsigned char *) b + EDIT_BUF_SIZE - 1 - i);
+
+ if (i == 0)
+ {
+ guint j;
+
+ j = buf->b2->len - 1;
+ b = g_ptr_array_index (buf->b2, j);
+ g_ptr_array_remove_index (buf->b2, j);
+ }
+
+ buf->curs2 = prev;
+
+ /* update file length */
+ buf->size--;
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Basic low level single character buffer alterations and movements at the cursor: delete character
+ * before the cursor position and move left.
+ *
+ * @param buf pointer to editor buffer
+ * @param c character to insert
+ */
+
+int
+edit_buffer_backspace (edit_buffer_t * buf)
+{
+ void *b;
+ unsigned char c;
+ off_t prev;
+ off_t i;
+
+ prev = buf->curs1 - 1;
+
+ b = g_ptr_array_index (buf->b1, prev >> S_EDIT_BUF_SIZE);
+ i = prev & M_EDIT_BUF_SIZE;
+ c = *((unsigned char *) b + i);
+
+ if (i == 0)
+ {
+ guint j;
+
+ j = buf->b1->len - 1;
+ b = g_ptr_array_index (buf->b1, j);
+ g_ptr_array_remove_index (buf->b1, j);
+ }
+
+ buf->curs1 = prev;
+
+ /* update file length */
+ buf->size--;
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate forward offset with specified number of lines.
+ *
+ * @param buf editor buffer
+ * @param current current offset
+ * @param lines number of lines to move forward
+ * @param upto offset to count lines between current and upto.
+ *
+ * @return If lines is zero returns the count of lines from current to upto.
+ * If upto is zero returns offset of lines forward current.
+ * Else returns forward offset with specified number of lines
+ */
+
+off_t
+edit_buffer_get_forward_offset (const edit_buffer_t * buf, off_t current, long lines, off_t upto)
+{
+ if (upto != 0)
+ return (off_t) edit_buffer_count_lines (buf, current, upto);
+
+ lines = MAX (lines, 0);
+
+ while (lines-- != 0)
+ {
+ long next;
+
+ next = edit_buffer_get_eol (buf, current) + 1;
+ if (next > buf->size)
+ break;
+ current = next;
+ }
+
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate backward offset with specified number of lines.
+ *
+ * @param buf editor buffer
+ * @param current current offset
+ * @param lines number of lines to move backward
+ *
+ * @return backward offset with specified number of lines.
+ */
+
+off_t
+edit_buffer_get_backward_offset (const edit_buffer_t * buf, off_t current, long lines)
+{
+ lines = MAX (lines, 0);
+ current = edit_buffer_get_bol (buf, current);
+
+ while (lines-- != 0 && current != 0)
+ current = edit_buffer_get_bol (buf, current - 1);
+
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load file into editor buffer
+ *
+ * @param buf pointer to editor buffer
+ * @param fd file descriptor
+ * @param size file size
+ *
+ * @return number of read bytes
+ */
+
+off_t
+edit_buffer_read_file (edit_buffer_t * buf, int fd, off_t size,
+ edit_buffer_read_file_status_msg_t * sm, gboolean * aborted)
+{
+ off_t ret = 0;
+ off_t i, j;
+ off_t data_size;
+ void *b;
+ status_msg_t *s = STATUS_MSG (sm);
+ unsigned short update_cnt = 0;
+
+ *aborted = FALSE;
+
+ buf->lines = 0;
+ buf->curs2 = size;
+ i = buf->curs2 >> S_EDIT_BUF_SIZE;
+
+ /* fill last part of b2 */
+ data_size = buf->curs2 & M_EDIT_BUF_SIZE;
+ if (data_size != 0)
+ {
+ b = g_malloc0 (EDIT_BUF_SIZE);
+ g_ptr_array_add (buf->b2, b);
+ b = (char *) b + EDIT_BUF_SIZE - data_size;
+ ret = mc_read (fd, b, data_size);
+
+ /* count lines */
+ for (j = 0; j < ret; j++)
+ if (*((char *) b + j) == '\n')
+ buf->lines++;
+
+ if (ret < 0 || ret != data_size)
+ return ret;
+ }
+
+ /* fulfill other parts of b2 from end to begin */
+ data_size = EDIT_BUF_SIZE;
+ for (--i; i >= 0; i--)
+ {
+ off_t sz;
+
+ b = g_malloc0 (data_size);
+ g_ptr_array_add (buf->b2, b);
+ sz = mc_read (fd, b, data_size);
+ if (sz >= 0)
+ ret += sz;
+
+ /* count lines */
+ for (j = 0; j < sz; j++)
+ if (*((char *) b + j) == '\n')
+ buf->lines++;
+
+ if (s != NULL && s->update != NULL)
+ {
+ update_cnt = (update_cnt + 1) & 0xf;
+ if (update_cnt == 0)
+ {
+ /* FIXME: overcare */
+ if (sm->buf == NULL)
+ sm->buf = buf;
+
+ sm->loaded = ret;
+ if (s->update (s) == B_CANCEL)
+ {
+ *aborted = TRUE;
+ return (-1);
+ }
+ }
+ }
+
+ if (sz != data_size)
+ break;
+ }
+
+ /* reverse buffer */
+ for (i = 0; i < (off_t) buf->b2->len / 2; i++)
+ {
+ void **b1, **b2;
+
+ b1 = &g_ptr_array_index (buf->b2, i);
+ b2 = &g_ptr_array_index (buf->b2, buf->b2->len - 1 - i);
+
+ b = *b1;
+ *b1 = *b2;
+ *b2 = b;
+
+ if (s != NULL && s->update != NULL)
+ {
+ update_cnt = (update_cnt + 1) & 0xf;
+ if (update_cnt == 0)
+ {
+ sm->loaded = ret;
+ if (s->update (s) == B_CANCEL)
+ {
+ *aborted = TRUE;
+ return (-1);
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Write editor buffer content to file
+ *
+ * @param buf pointer to editor buffer
+ * @param fd file descriptor
+ *
+ * @return number of written bytes
+ */
+
+off_t
+edit_buffer_write_file (edit_buffer_t * buf, int fd)
+{
+ off_t ret = 0;
+ off_t i;
+ off_t data_size, sz;
+ void *b;
+
+ /* write all fulfilled parts of b1 from begin to end */
+ if (buf->b1->len != 0)
+ {
+ data_size = EDIT_BUF_SIZE;
+ for (i = 0; i < (off_t) buf->b1->len - 1; i++)
+ {
+ b = g_ptr_array_index (buf->b1, i);
+ sz = mc_write (fd, b, data_size);
+ if (sz >= 0)
+ ret += sz;
+ else if (i == 0)
+ ret = sz;
+ if (sz != data_size)
+ return ret;
+ }
+
+ /* write last partially filled part of b1 */
+ data_size = ((buf->curs1 - 1) & M_EDIT_BUF_SIZE) + 1;
+ b = g_ptr_array_index (buf->b1, i);
+ sz = mc_write (fd, b, data_size);
+ if (sz >= 0)
+ ret += sz;
+ if (sz != data_size)
+ return ret;
+ }
+
+ /* write b2 from end to begin, if b2 contains some data */
+ if (buf->b2->len != 0)
+ {
+ /* write last partially filled part of b2 */
+ i = buf->b2->len - 1;
+ b = g_ptr_array_index (buf->b2, i);
+ data_size = ((buf->curs2 - 1) & M_EDIT_BUF_SIZE) + 1;
+ sz = mc_write (fd, (char *) b + EDIT_BUF_SIZE - data_size, data_size);
+ if (sz >= 0)
+ ret += sz;
+
+ if (sz == data_size)
+ {
+ /* write other fulfilled parts of b2 from end to begin */
+ data_size = EDIT_BUF_SIZE;
+ while (--i >= 0)
+ {
+ b = g_ptr_array_index (buf->b2, i);
+ sz = mc_write (fd, b, data_size);
+ if (sz >= 0)
+ ret += sz;
+ if (sz != data_size)
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate percentage of specified character offset
+ *
+ * @param buf pointer to editor buffer
+ * @param p character offset
+ *
+ * @return percentage of specified character offset
+ */
+
+int
+edit_buffer_calc_percent (const edit_buffer_t * buf, off_t offset)
+{
+ int percent;
+
+ if (buf->size == 0)
+ percent = 0;
+ else if (offset >= buf->size)
+ percent = 100;
+ else if (offset > (INT_MAX / 100))
+ percent = offset / (buf->size / 100);
+ else
+ percent = offset * 100 / buf->size;
+
+ return percent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editbuffer.h b/src/editor/editbuffer.h
new file mode 100644
index 0000000..def17ee
--- /dev/null
+++ b/src/editor/editbuffer.h
@@ -0,0 +1,117 @@
+/** \file
+ * \brief Header: text keep buffer for WEdit
+ */
+
+#ifndef MC__EDIT_BUFFER_H
+#define MC__EDIT_BUFFER_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct edit_buffer_struct
+{
+ off_t curs1; /* position of the cursor from the beginning of the file. */
+ off_t curs2; /* position from the end of the file */
+ GPtrArray *b1; /* all data up to curs1 */
+ GPtrArray *b2; /* all data from end of file down to curs2 */
+ off_t size; /* file size */
+ long lines; /* total lines in the file */
+ long curs_line; /* line number of the cursor. */
+} edit_buffer_t;
+
+typedef struct edit_buffer_read_file_status_msg_struct
+{
+ simple_status_msg_t status_msg; /* base class */
+
+ gboolean first;
+ edit_buffer_t *buf;
+ off_t loaded;
+} edit_buffer_read_file_status_msg_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void edit_buffer_init (edit_buffer_t * buf, off_t size);
+void edit_buffer_clean (edit_buffer_t * buf);
+
+int edit_buffer_get_byte (const edit_buffer_t * buf, off_t byte_index);
+#ifdef HAVE_CHARSET
+int edit_buffer_get_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length);
+int edit_buffer_get_prev_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length);
+#endif
+long edit_buffer_count_lines (const edit_buffer_t * buf, off_t first, off_t last);
+off_t edit_buffer_get_bol (const edit_buffer_t * buf, off_t current);
+off_t edit_buffer_get_eol (const edit_buffer_t * buf, off_t current);
+GString *edit_buffer_get_word_from_pos (const edit_buffer_t * buf, off_t start_pos, off_t * start,
+ gsize * cut);
+gboolean edit_buffer_find_word_start (const edit_buffer_t * buf, off_t * word_start,
+ gsize * word_len);
+
+void edit_buffer_insert (edit_buffer_t * buf, int c);
+void edit_buffer_insert_ahead (edit_buffer_t * buf, int c);
+int edit_buffer_delete (edit_buffer_t * buf);
+int edit_buffer_backspace (edit_buffer_t * buf);
+
+off_t edit_buffer_get_forward_offset (const edit_buffer_t * buf, off_t current, long lines,
+ off_t upto);
+off_t edit_buffer_get_backward_offset (const edit_buffer_t * buf, off_t current, long lines);
+
+off_t edit_buffer_read_file (edit_buffer_t * buf, int fd, off_t size,
+ edit_buffer_read_file_status_msg_t * sm, gboolean * aborted);
+off_t edit_buffer_write_file (edit_buffer_t * buf, int fd);
+
+int edit_buffer_calc_percent (const edit_buffer_t * buf, off_t offset);
+
+/*** inline functions ****************************************************************************/
+
+static inline int
+edit_buffer_get_current_byte (const edit_buffer_t * buf)
+{
+ return edit_buffer_get_byte (buf, buf->curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+edit_buffer_get_previous_byte (const edit_buffer_t * buf)
+{
+ return edit_buffer_get_byte (buf, buf->curs1 - 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get "begin-of-line" offset of current line
+ *
+ * @param buf editor buffer
+ *
+ * @return index of first char of current line
+ */
+
+static inline off_t
+edit_buffer_get_current_bol (const edit_buffer_t * buf)
+{
+ return edit_buffer_get_bol (buf, buf->curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get "end-of-line" offset of current line
+ *
+ * @param buf editor buffer
+ *
+ * @return index of first char of current line + 1
+ */
+
+static inline off_t
+edit_buffer_get_current_eol (const edit_buffer_t * buf)
+{
+ return edit_buffer_get_eol (buf, buf->curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__EDIT_BUFFER_H */
diff --git a/src/editor/editcmd.c b/src/editor/editcmd.c
new file mode 100644
index 0000000..de624f2
--- /dev/null
+++ b/src/editor/editcmd.c
@@ -0,0 +1,2108 @@
+/*
+ Editor high level editing commands
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2012-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2012
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: editor high level editing commands
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+/* #define PIPE_BLOCKS_SO_READ_BYTE_BY_BYTE */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL */
+#include "lib/strutil.h" /* utf string functions */
+#include "lib/fileloc.h"
+#include "lib/lock.h"
+#include "lib/util.h" /* tilde_expand() */
+#include "lib/vfs/vfs.h"
+#include "lib/widget.h"
+#include "lib/event.h" /* mc_event_raise() */
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/history.h"
+#include "src/file_history.h" /* show_file_history() */
+#ifdef HAVE_CHARSET
+#include "src/selcodepage.h"
+#endif
+#include "src/util.h" /* check_for_default() */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+#include "editsearch.h"
+#include "etags.h"
+
+/*** global variables ****************************************************************************/
+
+/* search and replace: */
+int search_create_bookmark = FALSE;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define space_width 1
+
+#define TEMP_BUF_LEN 1024
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static unsigned long edit_save_mode_radio_id, edit_save_mode_input_id;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_save_mode_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_CHANGED_FOCUS:
+ if (sender != NULL && sender->id == edit_save_mode_radio_id)
+ {
+ Widget *ww;
+
+ ww = widget_find_by_id (w, edit_save_mode_input_id);
+ widget_disable (ww, RADIO (sender)->sel != 2);
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* If 0 (quick save) then a) create/truncate <filename> file,
+ b) save to <filename>;
+ if 1 (safe save) then a) save to <tempnam>,
+ b) rename <tempnam> to <filename>;
+ if 2 (do backups) then a) save to <tempnam>,
+ b) rename <filename> to <filename.backup_ext>,
+ c) rename <tempnam> to <filename>. */
+
+/* returns 0 on error, -1 on abort */
+
+static int
+edit_save_file (WEdit * edit, const vfs_path_t * filename_vpath)
+{
+ char *p;
+ gchar *tmp;
+ off_t filelen = 0;
+ int this_save_mode, rv, fd = -1;
+ vfs_path_t *real_filename_vpath;
+ vfs_path_t *savename_vpath = NULL;
+ const char *start_filename;
+ const vfs_path_element_t *vpath_element;
+ struct stat sb;
+
+ vpath_element = vfs_path_get_by_index (filename_vpath, 0);
+ if (vpath_element == NULL)
+ return 0;
+
+ start_filename = vpath_element->path;
+ if (*start_filename == '\0')
+ return 0;
+
+ if (!IS_PATH_SEP (*start_filename) && edit->dir_vpath != NULL)
+ real_filename_vpath = vfs_path_append_vpath_new (edit->dir_vpath, filename_vpath, NULL);
+ else
+ real_filename_vpath = vfs_path_clone (filename_vpath);
+
+ this_save_mode = edit_options.save_mode;
+ if (this_save_mode != EDIT_QUICK_SAVE)
+ {
+ if (!vfs_file_is_local (real_filename_vpath))
+ /* The file does not exists yet, so no safe save or backup are necessary. */
+ this_save_mode = EDIT_QUICK_SAVE;
+ else
+ {
+ fd = mc_open (real_filename_vpath, O_RDONLY | O_BINARY);
+ if (fd == -1)
+ /* The file does not exists yet, so no safe save or backup are necessary. */
+ this_save_mode = EDIT_QUICK_SAVE;
+ }
+
+ if (fd != -1)
+ mc_close (fd);
+ }
+
+ rv = mc_stat (real_filename_vpath, &sb);
+ if (rv == 0)
+ {
+ if (this_save_mode == EDIT_QUICK_SAVE && !edit->skip_detach_prompt && sb.st_nlink > 1)
+ {
+ rv = edit_query_dialog3 (_("Warning"),
+ _("File has hard-links. Detach before saving?"),
+ _("&Yes"), _("&No"), _("&Cancel"));
+ switch (rv)
+ {
+ case 0:
+ this_save_mode = EDIT_SAFE_SAVE;
+ MC_FALLTHROUGH;
+ case 1:
+ edit->skip_detach_prompt = 1;
+ break;
+ default:
+ vfs_path_free (real_filename_vpath, TRUE);
+ return -1;
+ }
+ }
+
+ /* Prevent overwriting changes from other editor sessions. */
+ if (edit->stat1.st_mtime != 0 && edit->stat1.st_mtime != sb.st_mtime)
+ {
+ /* The default action is "Cancel". */
+ query_set_sel (1);
+
+ rv = edit_query_dialog2 (_("Warning"),
+ _("The file has been modified in the meantime. Save anyway?"),
+ _("&Yes"), _("&Cancel"));
+ if (rv != 0)
+ {
+ vfs_path_free (real_filename_vpath, TRUE);
+ return -1;
+ }
+ }
+ }
+
+ if (this_save_mode == EDIT_QUICK_SAVE)
+ savename_vpath = vfs_path_clone (real_filename_vpath);
+ else
+ {
+ char *savedir, *saveprefix;
+
+ savedir = vfs_path_tokens_get (real_filename_vpath, 0, -1);
+ if (savedir == NULL)
+ savedir = g_strdup (".");
+
+ /* Token-related function never return leading slash, so we need add it manually */
+ saveprefix = mc_build_filename (PATH_SEP_STR, savedir, "cooledit", (char *) NULL);
+ g_free (savedir);
+ fd = mc_mkstemps (&savename_vpath, saveprefix, NULL);
+ g_free (saveprefix);
+ if (savename_vpath == NULL)
+ {
+ vfs_path_free (real_filename_vpath, TRUE);
+ return 0;
+ }
+ /* FIXME:
+ * Close for now because mc_mkstemps use pure open system call
+ * to create temporary file and it needs to be reopened by
+ * VFS-aware mc_open().
+ */
+ close (fd);
+ }
+
+ (void) mc_chown (savename_vpath, edit->stat1.st_uid, edit->stat1.st_gid);
+ (void) mc_chmod (savename_vpath, edit->stat1.st_mode);
+
+ fd = mc_open (savename_vpath, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, edit->stat1.st_mode);
+ if (fd == -1)
+ goto error_save;
+
+ /* pipe save */
+ p = edit_get_write_filter (savename_vpath, real_filename_vpath);
+ if (p != NULL)
+ {
+ FILE *file;
+
+ mc_close (fd);
+ file = (FILE *) popen (p, "w");
+
+ if (file != NULL)
+ {
+ filelen = edit_write_stream (edit, file);
+#if 1
+ pclose (file);
+#else
+ if (pclose (file) != 0)
+ {
+ tmp = g_strdup_printf (_("Error writing to pipe: %s"), p);
+ edit_error_dialog (_("Error"), tmp);
+ g_free (tmp);
+ g_free (p);
+ goto error_save;
+ }
+#endif
+ }
+ else
+ {
+ tmp = g_strdup_printf (_("Cannot open pipe for writing: %s"), p);
+ edit_error_dialog (_("Error"), get_sys_error (tmp));
+ g_free (p);
+ g_free (tmp);
+ goto error_save;
+ }
+ g_free (p);
+ }
+ else if (edit->lb == LB_ASIS)
+ { /* do not change line breaks */
+ filelen = edit_buffer_write_file (&edit->buffer, fd);
+
+ if (filelen != edit->buffer.size)
+ {
+ mc_close (fd);
+ goto error_save;
+ }
+
+ if (mc_close (fd) != 0)
+ goto error_save;
+
+ /* Update the file information, especially the mtime. */
+ if (mc_stat (savename_vpath, &edit->stat1) == -1)
+ goto error_save;
+ }
+ else
+ { /* change line breaks */
+ FILE *file;
+ const char *savename;
+
+ mc_close (fd);
+
+ savename = vfs_path_get_last_path_str (savename_vpath);
+ file = (FILE *) fopen (savename, "w");
+ if (file != NULL)
+ {
+ filelen = edit_write_stream (edit, file);
+ fclose (file);
+ }
+ else
+ {
+ char *msg;
+
+ msg = g_strdup_printf (_("Cannot open file for writing: %s"), savename);
+ edit_error_dialog (_("Error"), msg);
+ g_free (msg);
+ goto error_save;
+ }
+ }
+
+ if (filelen != edit->buffer.size)
+ goto error_save;
+
+ if (this_save_mode == EDIT_DO_BACKUP)
+ {
+ char *tmp_store_filename;
+ vfs_path_element_t *last_vpath_element;
+ vfs_path_t *tmp_vpath;
+ gboolean ok;
+
+ g_assert (edit_options.backup_ext != NULL);
+
+ /* add backup extension to the path */
+ tmp_vpath = vfs_path_clone (real_filename_vpath);
+ last_vpath_element = (vfs_path_element_t *) vfs_path_get_by_index (tmp_vpath, -1);
+ tmp_store_filename = last_vpath_element->path;
+ last_vpath_element->path =
+ g_strdup_printf ("%s%s", tmp_store_filename, edit_options.backup_ext);
+ g_free (tmp_store_filename);
+
+ ok = (mc_rename (real_filename_vpath, tmp_vpath) != -1);
+ vfs_path_free (tmp_vpath, TRUE);
+ if (!ok)
+ goto error_save;
+ }
+
+ if (this_save_mode != EDIT_QUICK_SAVE && mc_rename (savename_vpath, real_filename_vpath) == -1)
+ goto error_save;
+
+ vfs_path_free (real_filename_vpath, TRUE);
+ vfs_path_free (savename_vpath, TRUE);
+ return 1;
+ error_save:
+ /* FIXME: Is this safe ?
+ * if (this_save_mode != EDIT_QUICK_SAVE)
+ * mc_unlink (savename);
+ */
+ vfs_path_free (real_filename_vpath, TRUE);
+ vfs_path_free (savename_vpath, TRUE);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+edit_check_newline (const edit_buffer_t * buf)
+{
+ return !(edit_options.check_nl_at_eof && buf->size > 0
+ && edit_buffer_get_byte (buf, buf->size - 1) != '\n'
+ && edit_query_dialog2 (_("Warning"),
+ _("The file you are saving does not end with a newline."),
+ _("C&ontinue"), _("&Cancel")) != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+edit_get_save_file_as (WEdit * edit)
+{
+ static LineBreaks cur_lb = LB_ASIS;
+ char *filename_res;
+ vfs_path_t *ret_vpath = NULL;
+
+ const char *lb_names[LB_NAMES] = {
+ N_("&Do not change"),
+ N_("&Unix format (LF)"),
+ N_("&Windows/DOS format (CR LF)"),
+ N_("&Macintosh format (CR)")
+ };
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Enter file name:"), input_label_above,
+ vfs_path_as_str (edit->filename_vpath), "save-as",
+ &filename_res, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_LABEL (N_("Change line breaks to:"), NULL),
+ QUICK_RADIO (LB_NAMES, lb_names, (int *) &cur_lb, NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 64 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Save As"), "[Save File As]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ {
+ char *fname;
+
+ edit->lb = cur_lb;
+ fname = tilde_expand (filename_res);
+ g_free (filename_res);
+ ret_vpath = vfs_path_from_str (fname);
+ g_free (fname);
+ }
+
+ return ret_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns TRUE on success */
+
+static gboolean
+edit_save_cmd (WEdit * edit)
+{
+ int res, save_lock = 0;
+
+ if (!edit->locked && !edit->delete_file)
+ save_lock = lock_file (edit->filename_vpath);
+ res = edit_save_file (edit, edit->filename_vpath);
+
+ /* Maintain modify (not save) lock on failure */
+ if ((res > 0 && edit->locked) || save_lock)
+ edit->locked = unlock_file (edit->filename_vpath);
+
+ /* On failure try 'save as', it does locking on its own */
+ if (res == 0)
+ return edit_save_as_cmd (edit);
+
+ if (res > 0)
+ {
+ edit->delete_file = 0;
+ edit->modified = 0;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_delete_column_of_text (WEdit * edit)
+{
+ off_t m1, m2;
+ off_t n;
+ long b, c, d;
+
+ eval_marks (edit, &m1, &m2);
+ n = edit_buffer_get_forward_offset (&edit->buffer, m1, 0, m2) + 1;
+ c = (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, m1), 0, m1);
+ d = (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, m2), 0, m2);
+ b = MAX (MIN (c, d), MIN (edit->column1, edit->column2));
+ c = MAX (c, MAX (edit->column1, edit->column2));
+
+ while (n-- != 0)
+ {
+ off_t r, p, q;
+
+ r = edit_buffer_get_current_bol (&edit->buffer);
+ p = edit_move_forward3 (edit, r, b, 0);
+ q = edit_move_forward3 (edit, r, c, 0);
+ p = MAX (p, m1);
+ q = MIN (q, m2);
+ edit_cursor_move (edit, p - edit->buffer.curs1);
+ /* delete line between margins */
+ for (; q > p; q--)
+ if (edit_buffer_get_current_byte (&edit->buffer) != '\n')
+ edit_delete (edit, TRUE);
+
+ /* move to next line except on the last delete */
+ if (n != 0)
+ edit_cursor_move (edit,
+ edit_buffer_get_forward_offset (&edit->buffer, edit->buffer.curs1, 1,
+ 0) - edit->buffer.curs1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** if success return 0 */
+
+static int
+edit_block_delete (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+ off_t curs_pos;
+ long curs_line, c1, c2;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return 0;
+
+ if (edit->column_highlight && edit->mark2 < 0)
+ edit_mark_cmd (edit, FALSE);
+
+ /* Warning message with a query to continue or cancel the operation */
+ if ((end_mark - start_mark) > max_undo / 2 &&
+ edit_query_dialog2 (_("Warning"),
+ ("Block is large, you may not be able to undo this action"),
+ _("C&ontinue"), _("&Cancel")) != 0)
+ return 1;
+
+ c1 = MIN (edit->column1, edit->column2);
+ c2 = MAX (edit->column1, edit->column2);
+ edit->column1 = c1;
+ edit->column2 = c2;
+
+ edit_push_markers (edit);
+
+ curs_line = edit->buffer.curs_line;
+
+ curs_pos = edit->curs_col + edit->over_col;
+
+ /* move cursor to start of selection */
+ edit_cursor_move (edit, start_mark - edit->buffer.curs1);
+ edit_scroll_screen_over_cursor (edit);
+
+ if (start_mark < end_mark)
+ {
+ if (edit->column_highlight)
+ {
+ off_t line_width;
+
+ if (edit->mark2 < 0)
+ edit_mark_cmd (edit, FALSE);
+ edit_delete_column_of_text (edit);
+ /* move cursor to the saved position */
+ edit_move_to_line (edit, curs_line);
+ /* calculate line width and cursor position before cut */
+ line_width = edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
+ edit_buffer_get_current_eol (&edit->buffer));
+ if (edit_options.cursor_beyond_eol && curs_pos > line_width)
+ edit->over_col = curs_pos - line_width;
+ }
+ else
+ {
+ off_t count;
+
+ for (count = start_mark; count < end_mark; count++)
+ edit_delete (edit, TRUE);
+ }
+ }
+
+ edit_set_markers (edit, 0, 0, 0, 0);
+ edit->force |= REDRAW_PAGE;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return a null terminated length of text. Result must be g_free'd */
+
+static unsigned char *
+edit_get_block (WEdit * edit, off_t start, off_t finish, off_t * l)
+{
+ unsigned char *s, *r;
+
+ r = s = g_malloc0 (finish - start + 1);
+
+ if (edit->column_highlight)
+ {
+ *l = 0;
+
+ /* copy from buffer, excluding chars that are out of the column 'margins' */
+ for (; start < finish; start++)
+ {
+ int c;
+ off_t x;
+
+ x = edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, start), 0, start);
+ c = edit_buffer_get_byte (&edit->buffer, start);
+ if ((x >= edit->column1 && x < edit->column2)
+ || (x >= edit->column2 && x < edit->column1) || c == '\n')
+ {
+ *s++ = c;
+ (*l)++;
+ }
+ }
+ }
+ else
+ {
+ *l = finish - start;
+
+ for (; start < finish; start++)
+ *s++ = edit_buffer_get_byte (&edit->buffer, start);
+ }
+
+ *s = '\0';
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** copies a block to clipboard file */
+
+static gboolean
+edit_save_block_to_clip_file (WEdit * edit, off_t start, off_t finish)
+{
+ gboolean ret;
+ gchar *tmp;
+
+ tmp = mc_config_get_full_path (EDIT_HOME_CLIP_FILE);
+ ret = edit_save_block (edit, tmp, start, finish);
+ g_free (tmp);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+pipe_mail (const edit_buffer_t * buf, char *to, char *subject, char *cc)
+{
+ FILE *p = 0;
+ char *s;
+
+ to = name_quote (to, FALSE);
+ subject = name_quote (subject, FALSE);
+ cc = name_quote (cc, FALSE);
+ s = g_strconcat ("mail -s ", subject, *cc ? " -c " : "", cc, " ", to, (char *) NULL);
+ g_free (to);
+ g_free (subject);
+ g_free (cc);
+
+ if (s != NULL)
+ {
+ p = popen (s, "w");
+ g_free (s);
+ }
+
+ if (p != NULL)
+ {
+ off_t i;
+
+ for (i = 0; i < buf->size; i++)
+ if (fputc (edit_buffer_get_byte (buf, i), p) < 0)
+ break;
+ pclose (p);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_insert_column_of_text (WEdit * edit, unsigned char *data, off_t size, long width,
+ off_t * start_pos, off_t * end_pos, long *col1, long *col2)
+{
+ off_t i, cursor;
+ long col;
+
+ cursor = edit->buffer.curs1;
+ col = edit_get_col (edit);
+
+ for (i = 0; i < size; i++)
+ {
+ if (data[i] != '\n')
+ edit_insert (edit, data[i]);
+ else
+ { /* fill in and move to next line */
+ long l;
+ off_t p;
+
+ if (edit_buffer_get_current_byte (&edit->buffer) != '\n')
+ {
+ for (l = width - (edit_get_col (edit) - col); l > 0; l -= space_width)
+ edit_insert (edit, ' ');
+ }
+ for (p = edit->buffer.curs1;; p++)
+ {
+ if (p == edit->buffer.size)
+ {
+ edit_cursor_move (edit, edit->buffer.size - edit->buffer.curs1);
+ edit_insert_ahead (edit, '\n');
+ p++;
+ break;
+ }
+ if (edit_buffer_get_byte (&edit->buffer, p) == '\n')
+ {
+ p++;
+ break;
+ }
+ }
+ edit_cursor_move (edit, edit_move_forward3 (edit, p, col, 0) - edit->buffer.curs1);
+
+ for (l = col - edit_get_col (edit); l >= space_width; l -= space_width)
+ edit_insert (edit, ' ');
+ }
+ }
+
+ *col1 = col;
+ *col2 = col + width;
+ *start_pos = cursor;
+ *end_pos = edit->buffer.curs1;
+ edit_cursor_move (edit, cursor - edit->buffer.curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for the iteration of objects in the 'editors' array.
+ * Toggle syntax highlighting in editor object.
+ *
+ * @param data probably WEdit object
+ * @param user_data unused
+ */
+
+static void
+edit_syntax_onoff_cb (void *data, void *user_data)
+{
+ (void) user_data;
+
+ if (edit_widget_is_editor (CONST_WIDGET (data)))
+ {
+ WEdit *edit = EDIT (data);
+
+ if (edit_options.syntax_highlighting)
+ edit_load_syntax (edit, NULL, edit->syntax_type);
+ edit->force |= REDRAW_PAGE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+editcmd_dialog_raw_key_query_cb (Widget * w, Widget * sender, widget_msg_t msg, int parm,
+ void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_KEY:
+ h->ret_value = parm;
+ dlg_close (h);
+ return MSG_HANDLED;
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_refresh_cmd (void)
+{
+ tty_clear_screen ();
+ repaint_screen ();
+ tty_keypad (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle syntax highlighting in all editor windows.
+ *
+ * @param h root widget for all windows
+ */
+
+void
+edit_syntax_onoff_cmd (WDialog * h)
+{
+ edit_options.syntax_highlighting = !edit_options.syntax_highlighting;
+ g_list_foreach (GROUP (h)->widgets, edit_syntax_onoff_cb, NULL);
+ widget_draw (WIDGET (h));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle tabs showing in all editor windows.
+ *
+ * @param h root widget for all windows
+ */
+
+void
+edit_show_tabs_tws_cmd (WDialog * h)
+{
+ enable_show_tabs_tws = !enable_show_tabs_tws;
+ widget_draw (WIDGET (h));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle right margin showing in all editor windows.
+ *
+ * @param h root widget for all windows
+ */
+
+void
+edit_show_margin_cmd (WDialog * h)
+{
+ edit_options.show_right_margin = !edit_options.show_right_margin;
+ widget_draw (WIDGET (h));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle line numbers showing in all editor windows.
+ *
+ * @param h root widget for all windows
+ */
+
+void
+edit_show_numbers_cmd (WDialog * h)
+{
+ edit_options.line_state = !edit_options.line_state;
+ edit_options.line_state_width = edit_options.line_state ? LINE_STATE_WIDTH : 0;
+ widget_draw (WIDGET (h));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_save_mode_cmd (void)
+{
+ char *str_result = NULL;
+
+ const char *str[] = {
+ N_("&Quick save"),
+ N_("&Safe save"),
+ N_("&Do backups with following extension:")
+ };
+
+#ifdef ENABLE_NLS
+ size_t i;
+
+ for (i = 0; i < 3; i++)
+ str[i] = _(str[i]);
+#endif
+
+ g_assert (edit_options.backup_ext != NULL);
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_RADIO (3, str, &edit_options.save_mode, &edit_save_mode_radio_id),
+ QUICK_INPUT (edit_options.backup_ext, "edit-backup-ext", &str_result,
+ &edit_save_mode_input_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_CHECKBOX (N_("Check &POSIX new line"), &edit_options.check_nl_at_eof, NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 38 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Edit Save Mode"), "[Edit Save Mode]",
+ quick_widgets, edit_save_mode_callback, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ {
+ g_free (edit_options.backup_ext);
+ edit_options.backup_ext = str_result;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_set_filename (WEdit * edit, const vfs_path_t * name_vpath)
+{
+ vfs_path_free (edit->filename_vpath, TRUE);
+ edit->filename_vpath = vfs_path_clone (name_vpath);
+
+ if (edit->dir_vpath == NULL)
+ edit->dir_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Here we want to warn the users of overwriting an existing file,
+ but only if they have made a change to the filename */
+/* returns TRUE on success */
+gboolean
+edit_save_as_cmd (WEdit * edit)
+{
+ /* This heads the 'Save As' dialog box */
+ vfs_path_t *exp_vpath;
+ int save_lock = 0;
+ gboolean different_filename = FALSE;
+ gboolean ret = FALSE;
+
+ if (!edit_check_newline (&edit->buffer))
+ return FALSE;
+
+ exp_vpath = edit_get_save_file_as (edit);
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ if (exp_vpath != NULL && vfs_path_len (exp_vpath) != 0)
+ {
+ int rv;
+
+ if (!vfs_path_equal (edit->filename_vpath, exp_vpath))
+ {
+ int file;
+ struct stat sb;
+
+ if (mc_stat (exp_vpath, &sb) == 0 && !S_ISREG (sb.st_mode))
+ {
+ edit_error_dialog (_("Save as"),
+ get_sys_error (_
+ ("Cannot save: destination is not a regular file")));
+ goto ret;
+ }
+
+ different_filename = TRUE;
+ file = mc_open (exp_vpath, O_RDONLY | O_BINARY);
+
+ if (file == -1)
+ edit->stat1.st_mode |= S_IWUSR;
+ else
+ {
+ /* the file exists */
+ mc_close (file);
+ /* Overwrite the current file or cancel the operation */
+ if (edit_query_dialog2
+ (_("Warning"),
+ _("A file already exists with this name"), _("&Overwrite"), _("&Cancel")))
+ goto ret;
+ }
+
+ save_lock = lock_file (exp_vpath);
+ }
+ else if (!edit->locked && !edit->delete_file)
+ /* filenames equal, check if already locked */
+ save_lock = lock_file (exp_vpath);
+
+ if (different_filename)
+ /* Allow user to write into saved (under another name) file
+ * even if original file had r/o user permissions. */
+ edit->stat1.st_mode |= S_IWUSR;
+
+ rv = edit_save_file (edit, exp_vpath);
+ switch (rv)
+ {
+ case 1:
+ /* Successful, so unlock both files */
+ if (different_filename)
+ {
+ if (save_lock)
+ unlock_file (exp_vpath);
+ if (edit->locked)
+ edit->locked = unlock_file (edit->filename_vpath);
+ }
+ else if (edit->locked || save_lock)
+ edit->locked = unlock_file (edit->filename_vpath);
+
+ edit_set_filename (edit, exp_vpath);
+ if (edit->lb != LB_ASIS)
+ edit_reload (edit, exp_vpath);
+ edit->modified = 0;
+ edit->delete_file = 0;
+ if (different_filename)
+ edit_load_syntax (edit, NULL, edit->syntax_type);
+ ret = TRUE;
+ break;
+
+ default:
+ edit_error_dialog (_("Save as"), get_sys_error (_("Cannot save file")));
+ MC_FALLTHROUGH;
+
+ case -1:
+ /* Failed, so maintain modify (not save) lock */
+ if (save_lock)
+ unlock_file (exp_vpath);
+ break;
+ }
+ }
+
+ ret:
+ vfs_path_free (exp_vpath, TRUE);
+ edit->force |= REDRAW_COMPLETELY;
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns TRUE on success */
+
+gboolean
+edit_save_confirm_cmd (WEdit * edit)
+{
+ if (edit->filename_vpath == NULL)
+ return edit_save_as_cmd (edit);
+
+ if (!edit_check_newline (&edit->buffer))
+ return FALSE;
+
+ if (edit_options.confirm_save)
+ {
+ char *f;
+ gboolean ok;
+
+ f = g_strdup_printf (_("Confirm save file: \"%s\""),
+ vfs_path_as_str (edit->filename_vpath));
+ ok = (edit_query_dialog2 (_("Save file"), f, _("&Save"), _("&Cancel")) == 0);
+ g_free (f);
+ if (!ok)
+ return FALSE;
+ }
+
+ return edit_save_cmd (edit);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Ask file to edit and load it.
+ *
+ * @return TRUE on success or cancel of ask.
+ */
+
+gboolean
+edit_load_cmd (WDialog * h)
+{
+ char *exp;
+ gboolean ret = TRUE; /* possible cancel */
+
+ exp = input_expand_dialog (_("Load"), _("Enter file name:"),
+ MC_HISTORY_EDIT_LOAD, INPUT_LAST_TEXT,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
+
+ if (exp != NULL && *exp != '\0')
+ {
+ vfs_path_t *exp_vpath;
+
+ exp_vpath = vfs_path_from_str (exp);
+ ret = edit_load_file_from_filename (h, exp_vpath, 0);
+ vfs_path_free (exp_vpath, TRUE);
+ }
+
+ g_free (exp);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load file content
+ *
+ * @param h screen the owner of editor window
+ * @param vpath vfs file path
+ * @param line line number
+ *
+ * @return TRUE if file content was successfully loaded, FALSE otherwise
+ */
+
+gboolean
+edit_load_file_from_filename (WDialog * h, const vfs_path_t * vpath, long line)
+{
+ WRect r = WIDGET (h)->rect;
+
+ rect_grow (&r, -1, 0);
+
+ return edit_add_window (h, &r, vpath, line);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show history od edited or viewed files and open selected file.
+ *
+ * @return TRUE on success, FALSE otherwise.
+ */
+
+gboolean
+edit_load_file_from_history (WDialog * h)
+{
+ char *exp;
+ int action;
+ gboolean ret = TRUE; /* possible cancel */
+
+ exp = show_file_history (CONST_WIDGET (h), &action);
+ if (exp != NULL && (action == CK_Edit || action == CK_Enter))
+ {
+ vfs_path_t *exp_vpath;
+
+ exp_vpath = vfs_path_from_str (exp);
+ ret = edit_load_file_from_filename (h, exp_vpath, 0);
+ vfs_path_free (exp_vpath, TRUE);
+ }
+
+ g_free (exp);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load syntax file to edit.
+ *
+ * @return TRUE on success
+ */
+
+gboolean
+edit_load_syntax_file (WDialog * h)
+{
+ vfs_path_t *extdir_vpath;
+ int dir = 0;
+ gboolean ret = FALSE;
+
+ if (geteuid () == 0)
+ dir = query_dialog (_("Syntax file edit"),
+ _("Which syntax file you want to edit?"), D_NORMAL, 2,
+ _("&User"), _("&System wide"));
+
+ extdir_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, EDIT_SYNTAX_FILE, (char *) NULL);
+ if (!exist_file (vfs_path_get_last_path_str (extdir_vpath)))
+ {
+ vfs_path_free (extdir_vpath, TRUE);
+ extdir_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, EDIT_SYNTAX_FILE, (char *) NULL);
+ }
+
+ if (dir == 0)
+ {
+ vfs_path_t *user_syntax_file_vpath;
+
+ user_syntax_file_vpath = mc_config_get_full_vpath (EDIT_SYNTAX_FILE);
+ check_for_default (extdir_vpath, user_syntax_file_vpath);
+ ret = edit_load_file_from_filename (h, user_syntax_file_vpath, 0);
+ vfs_path_free (user_syntax_file_vpath, TRUE);
+ }
+ else if (dir == 1)
+ ret = edit_load_file_from_filename (h, extdir_vpath, 0);
+
+ vfs_path_free (extdir_vpath, TRUE);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load menu file to edit.
+ *
+ * @return TRUE on success
+ */
+
+gboolean
+edit_load_menu_file (WDialog * h)
+{
+ vfs_path_t *buffer_vpath;
+ vfs_path_t *menufile_vpath;
+ int dir;
+ gboolean ret;
+
+ query_set_sel (1);
+ dir = query_dialog (_("Menu edit"),
+ _("Which menu file do you want to edit?"), D_NORMAL,
+ geteuid () != 0 ? 2 : 3, _("&Local"), _("&User"), _("&System wide"));
+
+ menufile_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, EDIT_GLOBAL_MENU, (char *) NULL);
+ if (!exist_file (vfs_path_get_last_path_str (menufile_vpath)))
+ {
+ vfs_path_free (menufile_vpath, TRUE);
+ menufile_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, EDIT_GLOBAL_MENU, (char *) NULL);
+ }
+
+ switch (dir)
+ {
+ case 0:
+ buffer_vpath = vfs_path_from_str (EDIT_LOCAL_MENU);
+ check_for_default (menufile_vpath, buffer_vpath);
+ chmod (vfs_path_get_last_path_str (buffer_vpath), 0600);
+ break;
+
+ case 1:
+ buffer_vpath = mc_config_get_full_vpath (EDIT_HOME_MENU);
+ check_for_default (menufile_vpath, buffer_vpath);
+ break;
+
+ case 2:
+ buffer_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, EDIT_GLOBAL_MENU, (char *) NULL);
+ if (!exist_file (vfs_path_get_last_path_str (buffer_vpath)))
+ {
+ vfs_path_free (buffer_vpath, TRUE);
+ buffer_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, EDIT_GLOBAL_MENU, (char *) NULL);
+ }
+ break;
+
+ default:
+ vfs_path_free (menufile_vpath, TRUE);
+ return FALSE;
+ }
+
+ ret = edit_load_file_from_filename (h, buffer_vpath, 0);
+
+ vfs_path_free (buffer_vpath, TRUE);
+ vfs_path_free (menufile_vpath, TRUE);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Close window with opened file.
+ *
+ * @return TRUE if file was closed.
+ */
+
+gboolean
+edit_close_cmd (WEdit * edit)
+{
+ gboolean ret;
+
+ ret = (edit != NULL) && edit_ok_to_exit (edit);
+
+ if (ret)
+ {
+ Widget *w = WIDGET (edit);
+ WGroup *g = w->owner;
+
+ if (edit->locked != 0)
+ unlock_file (edit->filename_vpath);
+
+ group_remove_widget (w);
+ widget_destroy (w);
+
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
+ edit = EDIT (g->current->data);
+ else
+ {
+ edit = edit_find_editor (DIALOG (g));
+ if (edit != NULL)
+ widget_select (WIDGET (edit));
+ }
+ }
+
+ if (edit != NULL)
+ edit->force |= REDRAW_COMPLETELY;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ if mark2 is -1 then marking is from mark1 to the cursor.
+ Otherwise its between the markers. This handles this.
+ Returns FALSE if no text is marked.
+ */
+
+gboolean
+eval_marks (WEdit * edit, off_t * start_mark, off_t * end_mark)
+{
+ long end_mark_curs;
+
+ if (edit->mark1 == edit->mark2)
+ {
+ *start_mark = *end_mark = 0;
+ edit->column2 = edit->column1 = 0;
+ return FALSE;
+ }
+
+ if (edit->end_mark_curs < 0)
+ end_mark_curs = edit->buffer.curs1;
+ else
+ end_mark_curs = edit->end_mark_curs;
+
+ if (edit->mark2 >= 0)
+ {
+ *start_mark = MIN (edit->mark1, edit->mark2);
+ *end_mark = MAX (edit->mark1, edit->mark2);
+ }
+ else
+ {
+ *start_mark = MIN (edit->mark1, end_mark_curs);
+ *end_mark = MAX (edit->mark1, end_mark_curs);
+ edit->column2 = edit->curs_col + edit->over_col;
+ }
+
+ if (edit->column_highlight
+ && ((edit->mark1 > end_mark_curs && edit->column1 < edit->column2)
+ || (edit->mark1 < end_mark_curs && edit->column1 > edit->column2)))
+ {
+ off_t start_bol, start_eol;
+ off_t end_bol, end_eol;
+ long col1, col2;
+ off_t diff1, diff2;
+
+ start_bol = edit_buffer_get_bol (&edit->buffer, *start_mark);
+ start_eol = edit_buffer_get_eol (&edit->buffer, start_bol - 1) + 1;
+ end_bol = edit_buffer_get_bol (&edit->buffer, *end_mark);
+ end_eol = edit_buffer_get_eol (&edit->buffer, *end_mark);
+ col1 = MIN (edit->column1, edit->column2);
+ col2 = MAX (edit->column1, edit->column2);
+
+ diff1 = edit_move_forward3 (edit, start_bol, col2, 0) -
+ edit_move_forward3 (edit, start_bol, col1, 0);
+ diff2 = edit_move_forward3 (edit, end_bol, col2, 0) -
+ edit_move_forward3 (edit, end_bol, col1, 0);
+
+ *start_mark -= diff1;
+ *end_mark += diff2;
+ *start_mark = MAX (*start_mark, start_eol);
+ *end_mark = MIN (*end_mark, end_eol);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_block_copy_cmd (WEdit * edit)
+{
+ off_t start_mark, end_mark, current = edit->buffer.curs1;
+ off_t mark1 = 0, mark2 = 0;
+ long c1 = 0, c2 = 0;
+ off_t size;
+ unsigned char *copy_buf;
+
+ edit_update_curs_col (edit);
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return;
+
+ copy_buf = edit_get_block (edit, start_mark, end_mark, &size);
+
+ /* all that gets pushed are deletes hence little space is used on the stack */
+
+ edit_push_markers (edit);
+
+ if (edit->column_highlight)
+ {
+ long col_delta;
+
+ col_delta = labs (edit->column2 - edit->column1);
+ edit_insert_column_of_text (edit, copy_buf, size, col_delta, &mark1, &mark2, &c1, &c2);
+ }
+ else
+ {
+ int size_orig = size;
+
+ while (size-- != 0)
+ edit_insert_ahead (edit, copy_buf[size]);
+
+ /* Place cursor at the end of text selection */
+ if (edit_options.cursor_after_inserted_block)
+ edit_cursor_move (edit, size_orig);
+ }
+
+ g_free (copy_buf);
+ edit_scroll_screen_over_cursor (edit);
+
+ if (edit->column_highlight)
+ edit_set_markers (edit, edit->buffer.curs1, mark2, c1, c2);
+ else if (start_mark < current && end_mark > current)
+ edit_set_markers (edit, start_mark, end_mark + end_mark - start_mark, 0, 0);
+
+ edit->force |= REDRAW_PAGE;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_block_move_cmd (WEdit * edit)
+{
+ off_t current;
+ unsigned char *copy_buf = NULL;
+ off_t start_mark, end_mark;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return;
+
+ if (!edit->column_highlight && edit->buffer.curs1 > start_mark && edit->buffer.curs1 < end_mark)
+ return;
+
+ if (edit->mark2 < 0)
+ edit_mark_cmd (edit, FALSE);
+ edit_push_markers (edit);
+
+ if (edit->column_highlight)
+ {
+ off_t mark1, mark2;
+ off_t size;
+ long c1, c2, b_width;
+ long x, x2;
+
+ c1 = MIN (edit->column1, edit->column2);
+ c2 = MAX (edit->column1, edit->column2);
+ b_width = c2 - c1;
+
+ edit_update_curs_col (edit);
+
+ x = edit->curs_col;
+ x2 = x + edit->over_col;
+
+ /* do nothing when cursor inside first line of selected area */
+ if ((edit_buffer_get_eol (&edit->buffer, edit->buffer.curs1) ==
+ edit_buffer_get_eol (&edit->buffer, start_mark)) && x2 > c1 && x2 <= c2)
+ return;
+
+ if (edit->buffer.curs1 > start_mark
+ && edit->buffer.curs1 < edit_buffer_get_eol (&edit->buffer, end_mark))
+ {
+ if (x > c2)
+ x -= b_width;
+ else if (x > c1 && x <= c2)
+ x = c1;
+ }
+ /* save current selection into buffer */
+ copy_buf = edit_get_block (edit, start_mark, end_mark, &size);
+
+ /* remove current selection */
+ edit_block_delete_cmd (edit);
+
+ edit->over_col = MAX (0, edit->over_col - b_width);
+ /* calculate the cursor pos after delete block */
+ current = edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), x, 0);
+ edit_cursor_move (edit, current - edit->buffer.curs1);
+ edit_scroll_screen_over_cursor (edit);
+
+ /* add TWS if need before block insertion */
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+
+ edit_insert_column_of_text (edit, copy_buf, size, b_width, &mark1, &mark2, &c1, &c2);
+ edit_set_markers (edit, mark1, mark2, c1, c2);
+ }
+ else
+ {
+ off_t count, count_orig;
+
+ current = edit->buffer.curs1;
+ copy_buf = g_malloc0 (end_mark - start_mark);
+ edit_cursor_move (edit, start_mark - edit->buffer.curs1);
+ edit_scroll_screen_over_cursor (edit);
+
+ for (count = start_mark; count < end_mark; count++)
+ copy_buf[end_mark - count - 1] = edit_delete (edit, TRUE);
+
+ edit_scroll_screen_over_cursor (edit);
+ edit_cursor_move (edit,
+ current - edit->buffer.curs1 -
+ (((current - edit->buffer.curs1) > 0) ? end_mark - start_mark : 0));
+ edit_scroll_screen_over_cursor (edit);
+ count_orig = count;
+ while (count-- > start_mark)
+ edit_insert_ahead (edit, copy_buf[end_mark - count - 1]);
+
+ edit_set_markers (edit, edit->buffer.curs1, edit->buffer.curs1 + end_mark - start_mark, 0,
+ 0);
+
+ /* Place cursor at the end of text selection */
+ if (edit_options.cursor_after_inserted_block)
+ edit_cursor_move (edit, count_orig - start_mark);
+ }
+
+ edit_scroll_screen_over_cursor (edit);
+ g_free (copy_buf);
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns 1 if canceelled by user */
+
+int
+edit_block_delete_cmd (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+
+ if (eval_marks (edit, &start_mark, &end_mark))
+ return edit_block_delete (edit);
+
+ edit_delete_line (edit);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check if it's OK to close the file. If there are unsaved changes, ask user.
+ *
+ * @return TRUE if it's OK to exit, FALSE to continue editing.
+ */
+
+gboolean
+edit_ok_to_exit (WEdit * edit)
+{
+ const char *fname = N_("[NoName]");
+ char *msg;
+ int act;
+
+ if (!edit->modified)
+ return TRUE;
+
+ if (edit->filename_vpath != NULL)
+ fname = vfs_path_as_str (edit->filename_vpath);
+#ifdef ENABLE_NLS
+ else
+ fname = _(fname);
+#endif
+
+ if (!mc_global.midnight_shutdown)
+ {
+ query_set_sel (2);
+
+ msg = g_strdup_printf (_("File %s was modified.\nSave before close?"), fname);
+ act = edit_query_dialog3 (_("Close file"), msg, _("&Yes"), _("&No"), _("&Cancel"));
+ }
+ else
+ {
+ msg = g_strdup_printf (_("Midnight Commander is being shut down.\nSave modified file %s?"),
+ fname);
+ act = edit_query_dialog2 (_("Quit"), msg, _("&Yes"), _("&No"));
+
+ /* Esc is No */
+ if (act == -1)
+ act = 1;
+ }
+
+ g_free (msg);
+
+ switch (act)
+ {
+ case 0: /* Yes */
+ if (!mc_global.midnight_shutdown && !edit_check_newline (&edit->buffer))
+ return FALSE;
+ edit_push_markers (edit);
+ edit_set_markers (edit, 0, 0, 0, 0);
+ if (!edit_save_cmd (edit) || mc_global.midnight_shutdown)
+ return mc_global.midnight_shutdown;
+ break;
+ case 1: /* No */
+ default:
+ break;
+ case 2: /* Cancel quit */
+ case -1: /* Esc */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** save block, returns TRUE on success */
+
+gboolean
+edit_save_block (WEdit * edit, const char *filename, off_t start, off_t finish)
+{
+ int file;
+ off_t len = 1;
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (filename);
+ file = mc_open (vpath, O_CREAT | O_WRONLY | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | O_BINARY);
+ vfs_path_free (vpath, TRUE);
+ if (file == -1)
+ return FALSE;
+
+ if (edit->column_highlight)
+ {
+ int r;
+
+ r = mc_write (file, VERTICAL_MAGIC, sizeof (VERTICAL_MAGIC));
+ if (r > 0)
+ {
+ unsigned char *block, *p;
+
+ p = block = edit_get_block (edit, start, finish, &len);
+ while (len != 0)
+ {
+ r = mc_write (file, p, len);
+ if (r < 0)
+ break;
+ p += r;
+ len -= r;
+ }
+ g_free (block);
+ }
+ }
+ else
+ {
+ unsigned char *buf;
+ off_t i = start;
+
+ len = finish - start;
+ buf = g_malloc0 (TEMP_BUF_LEN);
+ while (start != finish)
+ {
+ off_t end;
+
+ end = MIN (finish, start + TEMP_BUF_LEN);
+ for (; i < end; i++)
+ buf[i - start] = edit_buffer_get_byte (&edit->buffer, i);
+ len -= mc_write (file, (char *) buf, end - start);
+ start = end;
+ }
+ g_free (buf);
+ }
+ mc_close (file);
+
+ return (len == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_paste_from_history (WEdit * edit)
+{
+ (void) edit;
+ edit_error_dialog (_("Error"), _("This function is not implemented"));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_copy_to_X_buf_cmd (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return TRUE;
+
+ if (!edit_save_block_to_clip_file (edit, start_mark, end_mark))
+ {
+ edit_error_dialog (_("Copy to clipboard"), get_sys_error (_("Unable to save to file")));
+ return FALSE;
+ }
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
+
+ if (edit_options.drop_selection_on_copy)
+ edit_mark_cmd (edit, TRUE);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_cut_to_X_buf_cmd (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return TRUE;
+
+ if (!edit_save_block_to_clip_file (edit, start_mark, end_mark))
+ {
+ edit_error_dialog (_("Cut to clipboard"), _("Unable to save to file"));
+ return FALSE;
+ }
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
+
+ edit_block_delete_cmd (edit);
+ edit_mark_cmd (edit, TRUE);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_paste_from_X_buf_cmd (WEdit * edit)
+{
+ vfs_path_t *tmp;
+ gboolean ret;
+
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_from_ext_clip", NULL);
+ tmp = mc_config_get_full_vpath (EDIT_HOME_CLIP_FILE);
+ ret = (edit_insert_file (edit, tmp) >= 0);
+ vfs_path_free (tmp, TRUE);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Ask user for the line and go to that line.
+ * Negative numbers mean line from the end (i.e. -1 is the last line).
+ */
+
+void
+edit_goto_cmd (WEdit * edit)
+{
+ static gboolean first_run = TRUE;
+
+ char *f;
+ long l;
+ char *error;
+
+ f = input_dialog (_("Goto line"), _("Enter line:"), MC_HISTORY_EDIT_GOTO_LINE,
+ first_run ? NULL : INPUT_LAST_TEXT, INPUT_COMPLETE_NONE);
+ if (f == NULL || *f == '\0')
+ {
+ g_free (f);
+ return;
+ }
+
+ l = strtol (f, &error, 0);
+ if (*error != '\0')
+ {
+ g_free (f);
+ return;
+ }
+
+ if (l < 0)
+ l = edit->buffer.lines + l + 2;
+
+ edit_move_display (edit, l - WIDGET (edit)->rect.lines / 2 - 1);
+ edit_move_to_line (edit, l - 1);
+ edit->force |= REDRAW_COMPLETELY;
+
+ g_free (f);
+ first_run = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return TRUE on success */
+
+gboolean
+edit_save_block_cmd (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+ char *exp, *tmp;
+ gboolean ret = FALSE;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return TRUE;
+
+ tmp = mc_config_get_full_path (EDIT_HOME_CLIP_FILE);
+ exp =
+ input_expand_dialog (_("Save block"), _("Enter file name:"),
+ MC_HISTORY_EDIT_SAVE_BLOCK, tmp, INPUT_COMPLETE_FILENAMES);
+ g_free (tmp);
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ if (exp != NULL && *exp != '\0')
+ {
+ if (edit_save_block (edit, exp, start_mark, end_mark))
+ ret = TRUE;
+ else
+ edit_error_dialog (_("Save block"), get_sys_error (_("Cannot save file")));
+
+ edit->force |= REDRAW_COMPLETELY;
+ }
+
+ g_free (exp);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns TRUE on success */
+gboolean
+edit_insert_file_cmd (WEdit * edit)
+{
+ char *tmp;
+ char *exp;
+ gboolean ret = FALSE;
+
+ tmp = mc_config_get_full_path (EDIT_HOME_CLIP_FILE);
+ exp = input_expand_dialog (_("Insert file"), _("Enter file name:"),
+ MC_HISTORY_EDIT_INSERT_FILE, tmp, INPUT_COMPLETE_FILENAMES);
+ g_free (tmp);
+
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ if (exp != NULL && *exp != '\0')
+ {
+ vfs_path_t *exp_vpath;
+
+ exp_vpath = vfs_path_from_str (exp);
+ ret = (edit_insert_file (edit, exp_vpath) >= 0);
+ vfs_path_free (exp_vpath, TRUE);
+
+ if (!ret)
+ edit_error_dialog (_("Insert file"), get_sys_error (_("Cannot insert file")));
+ }
+
+ g_free (exp);
+
+ edit->force |= REDRAW_COMPLETELY;
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** sorts a block, returns -1 on system fail, 1 on cancel and 0 on success */
+
+int
+edit_sort_cmd (WEdit * edit)
+{
+ char *exp, *tmp, *tmp_edit_block_name, *tmp_edit_temp_name;
+ off_t start_mark, end_mark;
+ int e;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ {
+ edit_error_dialog (_("Sort block"), _("You must first highlight a block of text"));
+ return 0;
+ }
+
+ tmp = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
+ edit_save_block (edit, tmp, start_mark, end_mark);
+ g_free (tmp);
+
+ exp = input_dialog (_("Run sort"),
+ _("Enter sort options (see sort(1) manpage) separated by whitespace:"),
+ MC_HISTORY_EDIT_SORT, INPUT_LAST_TEXT, INPUT_COMPLETE_NONE);
+
+ if (exp == NULL)
+ return 1;
+
+ tmp_edit_block_name = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
+ tmp_edit_temp_name = mc_config_get_full_path (EDIT_HOME_TEMP_FILE);
+ tmp =
+ g_strconcat (" sort ", exp, " ", tmp_edit_block_name,
+ " > ", tmp_edit_temp_name, (char *) NULL);
+ g_free (tmp_edit_temp_name);
+ g_free (tmp_edit_block_name);
+ g_free (exp);
+
+ e = system (tmp);
+ g_free (tmp);
+ if (e != 0)
+ {
+ if (e == -1 || e == 127)
+ edit_error_dialog (_("Sort"), get_sys_error (_("Cannot execute sort command")));
+ else
+ {
+ char q[8];
+
+ sprintf (q, "%d ", e);
+ tmp = g_strdup_printf (_("Sort returned non-zero: %s"), q);
+ edit_error_dialog (_("Sort"), tmp);
+ g_free (tmp);
+ }
+
+ return -1;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+
+ if (edit_block_delete_cmd (edit))
+ return 1;
+
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = mc_config_get_full_vpath (EDIT_HOME_TEMP_FILE);
+ edit_insert_file (edit, tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Ask user for a command, execute it and paste its output back to the
+ * editor.
+ */
+
+int
+edit_ext_cmd (WEdit * edit)
+{
+ char *exp, *tmp, *tmp_edit_temp_file;
+ int e;
+
+ exp =
+ input_dialog (_("Paste output of external command"),
+ _("Enter shell command(s):"), MC_HISTORY_EDIT_PASTE_EXTCMD, INPUT_LAST_TEXT,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES
+ | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS |
+ INPUT_COMPLETE_SHELL_ESC);
+
+ if (!exp)
+ return 1;
+
+ tmp_edit_temp_file = mc_config_get_full_path (EDIT_HOME_TEMP_FILE);
+ tmp = g_strconcat (exp, " > ", tmp_edit_temp_file, (char *) NULL);
+ g_free (tmp_edit_temp_file);
+ e = system (tmp);
+ g_free (tmp);
+ g_free (exp);
+
+ if (e != 0)
+ {
+ edit_error_dialog (_("External command"), get_sys_error (_("Cannot execute command")));
+ return -1;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = mc_config_get_full_vpath (EDIT_HOME_TEMP_FILE);
+ edit_insert_file (edit, tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** if block is 1, a block must be highlighted and the shell command
+ processes it. If block is 0 the shell command is a straight system
+ command, that just produces some output which is to be inserted */
+
+void
+edit_block_process_cmd (WEdit * edit, int macro_number)
+{
+ char *fname;
+ char *macros_fname = NULL;
+
+ fname = g_strdup_printf ("%s.%i.sh", EDIT_HOME_MACRO_FILE, macro_number);
+ macros_fname = g_build_filename (mc_config_get_data_path (), fname, (char *) NULL);
+ user_menu (edit, macros_fname, 0);
+ g_free (fname);
+ g_free (macros_fname);
+ edit->force |= REDRAW_COMPLETELY;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_mail_dialog (WEdit * edit)
+{
+ char *mail_to, *mail_subject, *mail_cc;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABEL (N_("mail -s <subject> -c <cc> <to>"), NULL),
+ QUICK_LABELED_INPUT (N_("To"), input_label_above,
+ INPUT_LAST_TEXT, "mail-dlg-input-3",
+ &mail_to, NULL, FALSE, FALSE, INPUT_COMPLETE_USERNAMES),
+ QUICK_LABELED_INPUT (N_("Subject"), input_label_above,
+ INPUT_LAST_TEXT, "mail-dlg-input-2",
+ &mail_subject, NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_LABELED_INPUT (N_("Copies to"), input_label_above,
+ INPUT_LAST_TEXT, "mail-dlg-input",
+ &mail_cc, NULL, FALSE, FALSE, INPUT_COMPLETE_USERNAMES),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 50 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Mail"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ {
+ pipe_mail (&edit->buffer, mail_to, mail_subject, mail_cc);
+ g_free (mail_to);
+ g_free (mail_subject);
+ g_free (mail_cc);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+void
+edit_select_codepage_cmd (WEdit * edit)
+{
+ if (do_select_codepage ())
+ edit_set_codeset (edit);
+
+ edit->force = REDRAW_PAGE;
+ widget_draw (WIDGET (edit));
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_insert_literal_cmd (WEdit * edit)
+{
+ int char_for_insertion;
+
+ char_for_insertion = editcmd_dialog_raw_key_query (_("Insert literal"),
+ _("Press any key:"), FALSE);
+ edit_execute_key_command (edit, -1, ascii_alpha_to_cntrl (char_for_insertion));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_load_forward_cmd (WEdit * edit)
+{
+ if (edit->modified
+ && edit_query_dialog2 (_("Warning"),
+ _("Current text was modified without a file save.\n"
+ "Continue discards these changes."), _("C&ontinue"),
+ _("&Cancel")) == 1)
+ {
+ edit->force |= REDRAW_COMPLETELY;
+ return TRUE;
+ }
+
+ if (edit_stack_iterator + 1 >= MAX_HISTORY_MOVETO)
+ return FALSE;
+
+ if (edit_history_moveto[edit_stack_iterator + 1].line < 1)
+ return FALSE;
+
+ edit_stack_iterator++;
+ if (edit_history_moveto[edit_stack_iterator].filename_vpath != NULL)
+ return edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
+ edit_history_moveto[edit_stack_iterator].line);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_load_back_cmd (WEdit * edit)
+{
+ if (edit->modified
+ && edit_query_dialog2 (_("Warning"),
+ _("Current text was modified without a file save.\n"
+ "Continue discards these changes."), _("C&ontinue"),
+ _("&Cancel")) == 1)
+ {
+ edit->force |= REDRAW_COMPLETELY;
+ return TRUE;
+ }
+
+ /* we are in the bottom of the stack, NO WAY! */
+ if (edit_stack_iterator == 0)
+ return FALSE;
+
+ edit_stack_iterator--;
+ if (edit_history_moveto[edit_stack_iterator].filename_vpath != NULL)
+ return edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
+ edit_history_moveto[edit_stack_iterator].line);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* gets a raw key from the keyboard. Passing cancel = 1 draws
+ a cancel button thus allowing c-c etc. Alternatively, cancel = 0
+ will return the next key pressed. ctrl-a (=B_CANCEL), ctrl-g, ctrl-c,
+ and Esc are cannot returned */
+
+int
+editcmd_dialog_raw_key_query (const char *heading, const char *query, gboolean cancel)
+{
+ int w, wq;
+ int y = 2;
+ WDialog *raw_dlg;
+ WGroup *g;
+
+ w = str_term_width1 (heading) + 6;
+ wq = str_term_width1 (query);
+ w = MAX (w, wq + 3 * 2 + 1 + 2);
+
+ raw_dlg =
+ dlg_create (TRUE, 0, 0, cancel ? 7 : 5, w, WPOS_CENTER | WPOS_TRYUP, FALSE, dialog_colors,
+ editcmd_dialog_raw_key_query_cb, NULL, NULL, heading);
+ g = GROUP (raw_dlg);
+ widget_want_tab (WIDGET (raw_dlg), TRUE);
+
+ group_add_widget (g, label_new (y, 3, query));
+ group_add_widget (g,
+ input_new (y++, 3 + wq + 1, input_colors, w - (6 + wq + 1), "", 0,
+ INPUT_COMPLETE_NONE));
+ if (cancel)
+ {
+ group_add_widget (g, hline_new (y++, -1, -1));
+ /* Button w/o hotkey to allow use any key as raw or macro one */
+ group_add_widget_autopos (g, button_new (y, 1, B_CANCEL, NORMAL_BUTTON, _("Cancel"), NULL),
+ WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
+ }
+
+ w = dlg_run (raw_dlg);
+ widget_destroy (WIDGET (raw_dlg));
+
+ return (cancel && (w == ESC_CHAR || w == B_CANCEL)) ? 0 : w;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editcomplete.c b/src/editor/editcomplete.c
new file mode 100644
index 0000000..06f304d
--- /dev/null
+++ b/src/editor/editcomplete.c
@@ -0,0 +1,483 @@
+/*
+ Editor word completion engine
+
+ Copyright (C) 2021-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2021-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isspace() */
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/search.h"
+#include "lib/strutil.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* str_convert_to_input() */
+#endif
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/widget.h"
+
+#include "src/setup.h" /* verbose */
+
+#include "editwidget.h"
+#include "edit-impl.h"
+#include "editsearch.h"
+
+#include "editcomplete.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get current word under cursor
+ *
+ * @param esm status message window
+ * @param srch mc_search object
+ * @param word_start start word position
+ *
+ * @return newly allocated string or NULL if no any words under cursor
+ */
+
+static GString *
+edit_collect_completions_get_current_word (edit_search_status_msg_t * esm, mc_search_t * srch,
+ off_t word_start)
+{
+ WEdit *edit = esm->edit;
+ gsize len = 0;
+ GString *temp = NULL;
+
+ if (mc_search_run (srch, (void *) esm, word_start, edit->buffer.size, &len))
+ {
+ off_t i;
+
+ for (i = 0; i < (off_t) len; i++)
+ {
+ int chr;
+
+ chr = edit_buffer_get_byte (&edit->buffer, word_start + i);
+ if (!isspace (chr))
+ {
+ if (temp == NULL)
+ temp = g_string_sized_new (len);
+
+ g_string_append_c (temp, chr);
+ }
+ }
+ }
+
+ return temp;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * collect the possible completions from one buffer
+ */
+
+static void
+edit_collect_completion_from_one_buffer (gboolean active_buffer, GQueue ** compl,
+ mc_search_t * srch, edit_search_status_msg_t * esm,
+ off_t word_start, gsize word_len, off_t last_byte,
+ GString * current_word, int *max_width)
+{
+ GString *temp = NULL;
+ gsize len = 0;
+ off_t start = -1;
+
+ while (mc_search_run (srch, (void *) esm, start + 1, last_byte, &len))
+ {
+ gsize i;
+ int width;
+
+ if (temp == NULL)
+ temp = g_string_sized_new (8);
+ else
+ g_string_set_size (temp, 0);
+
+ start = srch->normal_offset;
+
+ /* add matched completion if not yet added */
+ for (i = 0; i < len; i++)
+ {
+ int ch;
+
+ ch = edit_buffer_get_byte (&esm->edit->buffer, start + i);
+ if (isspace (ch))
+ continue;
+
+ /* skip current word */
+ if (start + (off_t) i == word_start)
+ break;
+
+ g_string_append_c (temp, ch);
+ }
+
+ if (temp->len == 0)
+ continue;
+
+ if (current_word != NULL && g_string_equal (current_word, temp))
+ continue;
+
+ if (*compl == NULL)
+ *compl = g_queue_new ();
+ else
+ {
+ GList *l;
+
+ for (l = g_queue_peek_head_link (*compl); l != NULL; l = g_list_next (l))
+ {
+ GString *s = (GString *) l->data;
+
+ /* skip if already added */
+ if (strncmp (s->str + word_len, temp->str + word_len,
+ MAX (len, s->len) - word_len) == 0)
+ break;
+ }
+
+ if (l != NULL)
+ {
+ /* resort completion in main buffer only:
+ * these completions must be at the top of list in the completion dialog */
+ if (!active_buffer && l != g_queue_peek_tail_link (*compl))
+ {
+ /* move to the end */
+ g_queue_unlink (*compl, l);
+ g_queue_push_tail_link (*compl, l);
+ }
+
+ continue;
+ }
+ }
+
+#ifdef HAVE_CHARSET
+ {
+ GString *recoded;
+
+ recoded = str_nconvert_to_display (temp->str, temp->len);
+ if (recoded != NULL)
+ {
+ if (recoded->len != 0)
+ mc_g_string_copy (temp, recoded);
+
+ g_string_free (recoded, TRUE);
+ }
+ }
+#endif
+
+ if (active_buffer)
+ g_queue_push_tail (*compl, temp);
+ else
+ g_queue_push_head (*compl, temp);
+
+ start += len;
+
+ /* note the maximal length needed for the completion dialog */
+ width = str_term_width1 (temp->str);
+ *max_width = MAX (*max_width, width);
+
+ temp = NULL;
+ }
+
+ if (temp != NULL)
+ g_string_free (temp, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * collect the possible completions from all buffers
+ */
+
+static GQueue *
+edit_collect_completions (WEdit * edit, off_t word_start, gsize word_len,
+ const char *match_expr, int *max_width)
+{
+ GQueue *compl = NULL;
+ mc_search_t *srch;
+ off_t last_byte;
+ GString *current_word;
+ gboolean entire_file, all_files;
+ edit_search_status_msg_t esm;
+
+#ifdef HAVE_CHARSET
+ srch = mc_search_new (match_expr, cp_source);
+#else
+ srch = mc_search_new (match_expr, NULL);
+#endif
+ if (srch == NULL)
+ return NULL;
+
+ entire_file =
+ mc_config_get_bool (mc_global.main_config, CONFIG_APP_SECTION,
+ "editor_wordcompletion_collect_entire_file", FALSE);
+
+ last_byte = entire_file ? edit->buffer.size : word_start;
+
+ srch->search_type = MC_SEARCH_T_REGEX;
+ srch->is_case_sensitive = TRUE;
+ srch->search_fn = edit_search_cmd_callback;
+ srch->update_fn = edit_search_update_callback;
+
+ esm.first = TRUE;
+ esm.edit = edit;
+ esm.offset = entire_file ? 0 : word_start;
+
+ status_msg_init (STATUS_MSG (&esm), _("Collect completions"), 1.0, simple_status_msg_init_cb,
+ edit_search_status_update_cb, NULL);
+
+ current_word = edit_collect_completions_get_current_word (&esm, srch, word_start);
+
+ *max_width = 0;
+
+ /* collect completions from current buffer at first */
+ edit_collect_completion_from_one_buffer (TRUE, &compl, srch, &esm, word_start, word_len,
+ last_byte, current_word, max_width);
+
+ /* collect completions from other buffers */
+ all_files =
+ mc_config_get_bool (mc_global.main_config, CONFIG_APP_SECTION,
+ "editor_wordcompletion_collect_all_files", TRUE);
+ if (all_files)
+ {
+ const WGroup *owner = CONST_GROUP (CONST_WIDGET (edit)->owner);
+ gboolean saved_verbose;
+ GList *w;
+
+ /* don't show incorrect percentage in edit_search_status_update_cb() */
+ saved_verbose = verbose;
+ verbose = FALSE;
+
+ for (w = owner->widgets; w != NULL; w = g_list_next (w))
+ {
+ Widget *ww = WIDGET (w->data);
+ WEdit *e;
+
+ if (!edit_widget_is_editor (ww))
+ continue;
+
+ e = EDIT (ww);
+
+ if (e == edit)
+ continue;
+
+ /* search in entire file */
+ word_start = 0;
+ last_byte = e->buffer.size;
+ esm.edit = e;
+ esm.offset = 0;
+
+ edit_collect_completion_from_one_buffer (FALSE, &compl, srch, &esm, word_start,
+ word_len, last_byte, current_word, max_width);
+ }
+
+ verbose = saved_verbose;
+ }
+
+ status_msg_deinit (STATUS_MSG (&esm));
+ mc_search_free (srch);
+ if (current_word != NULL)
+ g_string_free (current_word, TRUE);
+
+ return compl;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Insert autocompleted word into editor.
+ *
+ * @param edit editor object
+ * @param completion word for completion
+ * @param word_len offset from beginning for insert
+ */
+
+static void
+edit_complete_word_insert_recoded_completion (WEdit * edit, char *completion, gsize word_len)
+{
+#ifdef HAVE_CHARSET
+ GString *temp;
+
+ temp = str_convert_to_input (completion);
+ if (temp != NULL)
+ {
+ for (completion = temp->str + word_len; *completion != '\0'; completion++)
+ edit_insert (edit, *completion);
+ g_string_free (temp, TRUE);
+ }
+#else
+ for (completion += word_len; *completion != '\0'; completion++)
+ edit_insert (edit, *completion);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_completion_string_free (gpointer data)
+{
+ g_string_free ((GString *) data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/* let the user select its preferred completion */
+
+/* Public function for unit tests */
+char *
+edit_completion_dialog_show (const WEdit * edit, GQueue * compl, int max_width)
+{
+ const WRect *we = &CONST_WIDGET (edit)->rect;
+ int start_x, start_y, offset;
+ char *curr = NULL;
+ WDialog *compl_dlg;
+ WListbox *compl_list;
+ int compl_dlg_h; /* completion dialog height */
+ int compl_dlg_w; /* completion dialog width */
+ GList *i;
+
+ /* calculate the dialog metrics */
+ compl_dlg_h = g_queue_get_length (compl) + 2;
+ compl_dlg_w = max_width + 4;
+ start_x = we->x + edit->curs_col + edit->start_col + EDIT_TEXT_HORIZONTAL_OFFSET +
+ (edit->fullscreen ? 0 : 1) + edit_options.line_state_width;
+ start_y = we->y + edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + (edit->fullscreen ? 0 : 1) + 1;
+
+ if (start_x < 0)
+ start_x = 0;
+ if (start_x < we->x + 1)
+ start_x = we->x + 1 + edit_options.line_state_width;
+ if (compl_dlg_w > COLS)
+ compl_dlg_w = COLS;
+ if (compl_dlg_h > LINES - 2)
+ compl_dlg_h = LINES - 2;
+
+ offset = start_x + compl_dlg_w - COLS;
+ if (offset > 0)
+ start_x -= offset;
+ offset = start_y + compl_dlg_h - LINES;
+ if (offset > 0)
+ start_y -= offset;
+
+ /* create the dialog */
+ compl_dlg =
+ dlg_create (TRUE, start_y, start_x, compl_dlg_h, compl_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, NULL, NULL, "[Completion]", NULL);
+
+ /* create the listbox */
+ compl_list = listbox_new (1, 1, compl_dlg_h - 2, compl_dlg_w - 2, FALSE, NULL);
+
+ /* fill the listbox with the completions in the reverse order */
+ for (i = g_queue_peek_tail_link (compl); i != NULL; i = g_list_previous (i))
+ listbox_add_item (compl_list, LISTBOX_APPEND_AT_END, 0, ((GString *) i->data)->str, NULL,
+ FALSE);
+
+ group_add_widget (GROUP (compl_dlg), compl_list);
+
+ /* pop up the dialog and apply the chosen completion */
+ if (dlg_run (compl_dlg) == B_ENTER)
+ {
+ listbox_get_current (compl_list, &curr, NULL);
+ curr = g_strdup (curr);
+ }
+
+ /* destroy dialog before return */
+ widget_destroy (WIDGET (compl_dlg));
+
+ return curr;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Complete current word using regular expression search
+ * backwards beginning at the current cursor position.
+ */
+
+void
+edit_complete_word_cmd (WEdit * edit)
+{
+ off_t word_start = 0;
+ gsize word_len = 0;
+ GString *match_expr;
+ gsize i;
+ GQueue *compl; /* completions: list of GString* */
+ int max_width;
+
+ /* search start of word to be completed */
+ if (!edit_buffer_find_word_start (&edit->buffer, &word_start, &word_len))
+ return;
+
+ /* prepare match expression */
+ /* match_expr = g_strdup_printf ("\\b%.*s[a-zA-Z_0-9]+", word_len, bufpos); */
+ match_expr = g_string_new ("(^|\\s+|\\b)");
+ for (i = 0; i < word_len; i++)
+ g_string_append_c (match_expr, edit_buffer_get_byte (&edit->buffer, word_start + i));
+ g_string_append (match_expr,
+ "[^\\s\\.=\\+\\[\\]\\(\\)\\,\\;\\:\\\"\\'\\-\\?\\/\\|\\\\\\{\\}\\*\\&\\^\\%%\\$#@\\!]+");
+
+ /* collect possible completions */
+ compl = edit_collect_completions (edit, word_start, word_len, match_expr->str, &max_width);
+
+ g_string_free (match_expr, TRUE);
+
+ if (compl == NULL)
+ return;
+
+ if (g_queue_get_length (compl) == 1)
+ {
+ /* insert completed word if there is only one match */
+
+ GString *curr_compl;
+
+ curr_compl = (GString *) g_queue_peek_head (compl);
+ edit_complete_word_insert_recoded_completion (edit, curr_compl->str, word_len);
+ }
+ else
+ {
+ /* more than one possible completion => ask the user */
+
+ char *curr_compl;
+
+ /* let the user select the preferred completion */
+ curr_compl = edit_completion_dialog_show (edit, compl, max_width);
+ if (curr_compl != NULL)
+ {
+ edit_complete_word_insert_recoded_completion (edit, curr_compl, word_len);
+ g_free (curr_compl);
+ }
+ }
+
+ g_queue_free_full (compl, edit_completion_string_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editcomplete.h b/src/editor/editcomplete.h
new file mode 100644
index 0000000..90ded8d
--- /dev/null
+++ b/src/editor/editcomplete.h
@@ -0,0 +1,21 @@
+#ifndef MC__EDIT_COMPLETE_H
+#define MC__EDIT_COMPLETE_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* Public function for unit tests */
+char *edit_completion_dialog_show (const WEdit * edit, GQueue * compl, int max_width);
+
+void edit_complete_word_cmd (WEdit * edit);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EDIT_COMPLETE_H */
diff --git a/src/editor/editdraw.c b/src/editor/editdraw.c
new file mode 100644
index 0000000..fbd1e09
--- /dev/null
+++ b/src/editor/editdraw.c
@@ -0,0 +1,1122 @@
+/*
+ Editor text drawing.
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru> 2012-2022
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: editor text drawing
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h" /* tty_printf() */
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/skin.h"
+#include "lib/strutil.h" /* utf string functions */
+#include "lib/util.h" /* is_printable() */
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "edit-impl.h"
+#include "editwidget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MAX_LINE_LEN 1024
+
+/* Text styles */
+#define MOD_ABNORMAL (1 << 8)
+#define MOD_BOLD (1 << 9)
+#define MOD_MARKED (1 << 10)
+#define MOD_CURSOR (1 << 11)
+#define MOD_WHITESPACE (1 << 12)
+
+#define edit_move(x,y) widget_gotoyx(edit, y, x);
+
+#define key_pending(x) (!is_idle())
+
+#define EDITOR_MINIMUM_TERMINAL_WIDTH 30
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ unsigned int ch;
+ unsigned int style;
+} line_s;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+static inline void
+printwstr (const char *s, int len)
+{
+ if (len > 0)
+ tty_printf ("%-*.*s", len, len, s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+status_string (WEdit * edit, char *s, int w)
+{
+ char byte_str[16];
+
+ /*
+ * If we are at the end of file, print <EOF>,
+ * otherwise print the current character as is (if printable),
+ * as decimal and as hex.
+ */
+ if (edit->buffer.curs1 >= edit->buffer.size)
+ strcpy (byte_str, "<EOF> ");
+#ifdef HAVE_CHARSET
+ else if (edit->utf8)
+ {
+ unsigned int cur_utf;
+ int char_length = 1;
+
+ cur_utf = edit_buffer_get_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length > 0)
+ g_snprintf (byte_str, sizeof (byte_str), "%04u 0x%03X",
+ (unsigned) cur_utf, (unsigned) cur_utf);
+ else
+ {
+ cur_utf = edit_buffer_get_current_byte (&edit->buffer);
+ g_snprintf (byte_str, sizeof (byte_str), "%04d 0x%03X",
+ (int) cur_utf, (unsigned) cur_utf);
+ }
+ }
+#endif
+ else
+ {
+ unsigned char cur_byte;
+
+ cur_byte = edit_buffer_get_current_byte (&edit->buffer);
+ g_snprintf (byte_str, sizeof (byte_str), "%4d 0x%03X", (int) cur_byte, (unsigned) cur_byte);
+ }
+
+ /* The field lengths just prevent the status line from shortening too much */
+ if (edit_options.simple_statusbar)
+ g_snprintf (s, w,
+ "%c%c%c%c %3ld %5ld/%ld %6ld/%ld %s %s",
+ edit->mark1 != edit->mark2 ? (edit->column_highlight ? 'C' : 'B') : '-',
+ edit->modified ? 'M' : '-',
+ macro_index < 0 ? '-' : 'R',
+ edit->overwrite == 0 ? '-' : 'O',
+ edit->curs_col + edit->over_col,
+ edit->buffer.curs_line + 1,
+ edit->buffer.lines + 1, (long) edit->buffer.curs1, (long) edit->buffer.size,
+ byte_str,
+#ifdef HAVE_CHARSET
+ mc_global.source_codepage >= 0 ? get_codepage_id (mc_global.source_codepage) :
+#endif
+ "");
+ else
+ g_snprintf (s, w,
+ "[%c%c%c%c] %2ld L:[%3ld+%2ld %3ld/%3ld] *(%-4ld/%4ldb) %s %s",
+ edit->mark1 != edit->mark2 ? (edit->column_highlight ? 'C' : 'B') : '-',
+ edit->modified ? 'M' : '-',
+ macro_index < 0 ? '-' : 'R',
+ edit->overwrite == 0 ? '-' : 'O',
+ edit->curs_col + edit->over_col,
+ edit->start_line + 1,
+ edit->curs_row,
+ edit->buffer.curs_line + 1,
+ edit->buffer.lines + 1, (long) edit->buffer.curs1, (long) edit->buffer.size,
+ byte_str,
+#ifdef HAVE_CHARSET
+ mc_global.source_codepage >= 0 ? get_codepage_id (mc_global.source_codepage) :
+#endif
+ "");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Draw the status line at the top of the screen for fullscreen editor window.
+ *
+ * @param edit editor object
+ * @param color color pair
+ */
+
+static inline void
+edit_status_fullscreen (WEdit * edit, int color)
+{
+ Widget *h = WIDGET (WIDGET (edit)->owner);
+ const int w = h->rect.cols;
+ const int gap = 3; /* between the filename and the status */
+ const int right_gap = 5; /* at the right end of the screen */
+ const int preferred_fname_len = 16;
+ char *status;
+ size_t status_size;
+ int status_len;
+ const char *fname = "";
+ int fname_len;
+
+ status_size = w + 1;
+ status = g_malloc (status_size);
+ status_string (edit, status, status_size);
+ status_len = (int) str_term_width1 (status);
+
+ if (edit->filename_vpath != NULL)
+ {
+ fname = vfs_path_get_last_path_str (edit->filename_vpath);
+
+ if (!edit_options.state_full_filename)
+ fname = x_basename (fname);
+ }
+
+ fname_len = str_term_width1 (fname);
+ if (fname_len < preferred_fname_len)
+ fname_len = preferred_fname_len;
+
+ if (fname_len + gap + status_len + right_gap >= w)
+ {
+ if (preferred_fname_len + gap + status_len + right_gap >= w)
+ fname_len = preferred_fname_len;
+ else
+ fname_len = w - (gap + status_len + right_gap);
+ fname = str_trunc (fname, fname_len);
+ }
+
+ widget_gotoyx (h, 0, 0);
+ tty_setcolor (color);
+ printwstr (fname, fname_len + gap);
+ printwstr (status, w - (fname_len + gap));
+
+ if (edit_options.simple_statusbar && w > EDITOR_MINIMUM_TERMINAL_WIDTH)
+ {
+ int percent;
+
+ percent = edit_buffer_calc_percent (&edit->buffer, edit->buffer.curs1);
+ widget_gotoyx (h, 0, w - 6 - 6);
+ tty_printf (" %3d%%", percent);
+ }
+
+ g_free (status);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Draw status line for editor window if window is not in fullscreen mode.
+ *
+ * @param edit editor object
+ */
+
+static inline void
+edit_status_window (WEdit * edit)
+{
+ Widget *w = WIDGET (edit);
+ int y, x;
+ int cols = w->rect.cols;
+
+ tty_setcolor (STATUSBAR_COLOR);
+
+ if (cols > 5)
+ {
+ const char *fname = N_("NoName");
+
+ if (edit->filename_vpath != NULL)
+ {
+ fname = vfs_path_get_last_path_str (edit->filename_vpath);
+
+ if (!edit_options.state_full_filename)
+ fname = x_basename (fname);
+ }
+#ifdef ENABLE_NLS
+ else
+ fname = _(fname);
+#endif
+
+ edit_move (2, 0);
+ tty_printf ("[%s]", str_term_trim (fname, w->rect.cols - 8 - 6));
+ }
+
+ tty_getyx (&y, &x);
+ x -= w->rect.x;
+ x += 4;
+ if (x + 6 <= cols - 2 - 6)
+ {
+ edit_move (x, 0);
+ tty_printf ("[%c%c%c%c]",
+ edit->mark1 != edit->mark2 ? (edit->column_highlight ? 'C' : 'B') : '-',
+ edit->modified ? 'M' : '-',
+ macro_index < 0 ? '-' : 'R', edit->overwrite == 0 ? '-' : 'O');
+ }
+
+ if (cols > 30)
+ {
+ edit_move (2, w->rect.lines - 1);
+ tty_printf ("%3ld %5ld/%ld %6ld/%ld",
+ edit->curs_col + edit->over_col,
+ edit->buffer.curs_line + 1, edit->buffer.lines + 1, (long) edit->buffer.curs1,
+ (long) edit->buffer.size);
+ }
+
+ /*
+ * If we are at the end of file, print <EOF>,
+ * otherwise print the current character as is (if printable),
+ * as decimal and as hex.
+ */
+ if (cols > 46)
+ {
+ edit_move (32, w->rect.lines - 1);
+ if (edit->buffer.curs1 >= edit->buffer.size)
+ tty_print_string ("[<EOF> ]");
+#ifdef HAVE_CHARSET
+ else if (edit->utf8)
+ {
+ unsigned int cur_utf;
+ int char_length = 1;
+
+ cur_utf = edit_buffer_get_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length <= 0)
+ cur_utf = edit_buffer_get_current_byte (&edit->buffer);
+ tty_printf ("[%05u 0x%04X]", cur_utf, cur_utf);
+ }
+#endif
+ else
+ {
+ unsigned char cur_byte;
+
+ cur_byte = edit_buffer_get_current_byte (&edit->buffer);
+ tty_printf ("[%05u 0x%04X]", (unsigned int) cur_byte, (unsigned int) cur_byte);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Draw a frame around edit area.
+ *
+ * @param edit editor object
+ * @param color color pair
+ * @param active TRUE if editor object is focused
+ */
+
+static inline void
+edit_draw_frame (const WEdit * edit, int color, gboolean active)
+{
+ const Widget *w = CONST_WIDGET (edit);
+
+ /* draw a frame around edit area */
+ tty_setcolor (color);
+ /* draw double frame for active window if skin supports that */
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, !active);
+ /* draw a drag marker */
+ if (edit->drag_state == MCEDIT_DRAG_NONE)
+ {
+ tty_setcolor (EDITOR_FRAME_DRAG);
+ widget_gotoyx (w, w->rect.lines - 1, w->rect.cols - 1);
+ tty_print_alt_char (ACS_LRCORNER, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Draw a window control buttons.
+ *
+ * @param edit editor object
+ * @param color color pair
+ */
+
+static inline void
+edit_draw_window_icons (const WEdit * edit, int color)
+{
+ const Widget *w = CONST_WIDGET (edit);
+ char tmp[17];
+
+ tty_setcolor (color);
+ if (edit->fullscreen)
+ widget_gotoyx (w->owner, 0, WIDGET (w->owner)->rect.cols - 6);
+ else
+ widget_gotoyx (w, 0, w->rect.cols - 8);
+ g_snprintf (tmp, sizeof (tmp), "[%s][%s]", edit_window_state_char, edit_window_close_char);
+ tty_print_string (tmp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+print_to_widget (WEdit * edit, long row, int start_col, int start_col_real,
+ long end_col, line_s line[], char *status, int bookmarked)
+{
+ Widget *w = WIDGET (edit);
+ line_s *p;
+ int x, x1, y, cols_to_skip;
+ int i;
+ int wrap_start;
+ int len;
+
+ x = start_col_real;
+ x1 = start_col + EDIT_TEXT_HORIZONTAL_OFFSET + edit_options.line_state_width;
+ y = row + EDIT_TEXT_VERTICAL_OFFSET;
+ cols_to_skip = abs (x);
+
+ if (!edit->fullscreen)
+ {
+ x1++;
+ y++;
+ }
+
+ tty_setcolor (EDITOR_NORMAL_COLOR);
+ if (bookmarked != 0)
+ tty_setcolor (bookmarked);
+
+ len = end_col + 1 - start_col;
+ wrap_start = edit_options.word_wrap_line_length + edit->start_col;
+
+ if (len > 0 && w->rect.y + y >= 0)
+ {
+ if (!edit_options.show_right_margin || wrap_start > end_col)
+ tty_draw_hline (w->rect.y + y, w->rect.x + x1, ' ', len);
+ else if (wrap_start < 0)
+ {
+ tty_setcolor (EDITOR_RIGHT_MARGIN_COLOR);
+ tty_draw_hline (w->rect.y + y, w->rect.x + x1, ' ', len);
+ }
+ else
+ {
+ if (wrap_start > 0)
+ tty_draw_hline (w->rect.y + y, w->rect.x + x1, ' ', wrap_start);
+
+ len -= wrap_start;
+ if (len > 0)
+ {
+ tty_setcolor (EDITOR_RIGHT_MARGIN_COLOR);
+ tty_draw_hline (w->rect.y + y, w->rect.x + x1 + wrap_start, ' ', len);
+ }
+ }
+ }
+
+ if (edit_options.line_state)
+ {
+ tty_setcolor (LINE_STATE_COLOR);
+
+ for (i = 0; i < LINE_STATE_WIDTH; i++)
+ {
+ edit_move (x1 + i - edit_options.line_state_width, y);
+ if (status[i] == '\0')
+ status[i] = ' ';
+ tty_print_char (status[i]);
+ }
+ }
+
+ edit_move (x1, y);
+
+ i = 1;
+ for (p = line; p->ch != 0; p++)
+ {
+ int style;
+ unsigned int textchar;
+ int color;
+
+ if (cols_to_skip != 0)
+ {
+ cols_to_skip--;
+ continue;
+ }
+
+ style = p->style & 0xFF00;
+ textchar = p->ch;
+ /* If non-printable - use black background */
+ color = (style & MOD_ABNORMAL) != 0 ? 0 : p->style >> 16;
+
+ if ((style & MOD_WHITESPACE) != 0)
+ {
+ if ((style & MOD_MARKED) == 0)
+ tty_setcolor (EDITOR_WHITESPACE_COLOR);
+ else
+ {
+ textchar = ' ';
+ tty_setcolor (EDITOR_MARKED_COLOR);
+ }
+ }
+ else if ((style & MOD_BOLD) != 0)
+ tty_setcolor (EDITOR_BOLD_COLOR);
+ else if ((style & MOD_MARKED) != 0)
+ tty_setcolor (EDITOR_MARKED_COLOR);
+ else
+ tty_lowlevel_setcolor (color);
+
+ if (edit_options.show_right_margin)
+ {
+ if (i > edit_options.word_wrap_line_length + edit->start_col)
+ tty_setcolor (EDITOR_RIGHT_MARGIN_COLOR);
+ i++;
+ }
+
+ tty_print_anychar (textchar);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** b is a pointer to the beginning of the line */
+
+static void
+edit_draw_this_line (WEdit * edit, off_t b, long row, long start_col, long end_col)
+{
+ Widget *w = WIDGET (edit);
+ line_s line[MAX_LINE_LEN];
+ line_s *p = line;
+ off_t q;
+ int col, start_col_real;
+ int abn_style;
+ int book_mark = 0;
+ char line_stat[LINE_STATE_WIDTH + 1] = "\0";
+
+ if (row > w->rect.lines - 1 - EDIT_TEXT_VERTICAL_OFFSET - 2 * (edit->fullscreen ? 0 : 1))
+ return;
+
+ if (book_mark_query_color (edit, edit->start_line + row, BOOK_MARK_COLOR))
+ book_mark = BOOK_MARK_COLOR;
+ else if (book_mark_query_color (edit, edit->start_line + row, BOOK_MARK_FOUND_COLOR))
+ book_mark = BOOK_MARK_FOUND_COLOR;
+
+ if (book_mark != 0)
+ abn_style = book_mark << 16;
+ else
+ abn_style = MOD_ABNORMAL;
+
+ end_col -= EDIT_TEXT_HORIZONTAL_OFFSET + edit_options.line_state_width;
+ if (!edit->fullscreen)
+ {
+ end_col--;
+ if (w->rect.x + w->rect.cols <= WIDGET (w->owner)->rect.cols)
+ end_col--;
+ }
+
+ q = edit_move_forward3 (edit, b, start_col - edit->start_col, 0);
+ col = (int) edit_move_forward3 (edit, b, 0, q);
+ start_col_real = col + edit->start_col;
+
+ if (edit_options.line_state)
+ {
+ long cur_line;
+
+ cur_line = edit->start_line + row;
+ if (cur_line <= edit->buffer.lines)
+ g_snprintf (line_stat, sizeof (line_stat), "%7ld ", cur_line + 1);
+ else
+ {
+ memset (line_stat, ' ', LINE_STATE_WIDTH);
+ line_stat[LINE_STATE_WIDTH] = '\0';
+ }
+
+ if (book_mark_query_color (edit, cur_line, BOOK_MARK_COLOR))
+ g_snprintf (line_stat, 2, "*");
+ }
+
+ if (col <= -(edit->start_col + 16))
+ start_col_real = start_col = 0;
+ else
+ {
+ off_t m1 = 0, m2 = 0;
+
+ eval_marks (edit, &m1, &m2);
+
+ if (row <= edit->buffer.lines - edit->start_line)
+ {
+ off_t tws = 0;
+
+ if (edit_options.visible_tws && tty_use_colors ())
+ for (tws = edit_buffer_get_eol (&edit->buffer, b); tws > b; tws--)
+ {
+ unsigned int c;
+
+ c = edit_buffer_get_byte (&edit->buffer, tws - 1);
+ if (!whitespace (c))
+ break;
+ }
+
+ while (col <= end_col - edit->start_col)
+ {
+ int char_length = 1;
+ unsigned int c;
+ gboolean wide_width_char = FALSE;
+ gboolean control_char = FALSE;
+
+ p->ch = 0;
+ p->style = q == edit->buffer.curs1 ? MOD_CURSOR : 0;
+
+ if (q >= m1 && q < m2)
+ {
+ if (!edit->column_highlight)
+ p->style |= MOD_MARKED;
+ else
+ {
+ long x, cl;
+
+ x = (long) edit_move_forward3 (edit, b, 0, q);
+ cl = MIN (edit->column1, edit->column2);
+ if (x >= cl)
+ {
+ cl = MAX (edit->column1, edit->column2);
+ if (x < cl)
+ p->style |= MOD_MARKED;
+ }
+ }
+ }
+
+ if (q == edit->bracket)
+ p->style |= MOD_BOLD;
+ if (q >= edit->found_start && q < (off_t) (edit->found_start + edit->found_len))
+ p->style |= MOD_BOLD;
+
+#ifdef HAVE_CHARSET
+ if (edit->utf8)
+ c = edit_buffer_get_utf (&edit->buffer, q, &char_length);
+ else
+#endif
+ c = edit_buffer_get_byte (&edit->buffer, q);
+
+ /* we don't use bg for mc - fg contains both */
+ if (book_mark != 0)
+ p->style |= book_mark << 16;
+ else
+ {
+ int color;
+
+ color = edit_get_syntax_color (edit, q);
+ p->style |= color << 16;
+ }
+
+ switch (c)
+ {
+ case '\n':
+ col = end_col - edit->start_col + 1; /* quit */
+ break;
+
+ case '\t':
+ {
+ int tab_over;
+ int i;
+
+ i = TAB_SIZE - ((int) col % TAB_SIZE);
+ tab_over = (end_col - edit->start_col) - (col + i - 1);
+ if (tab_over < 0)
+ i += tab_over;
+ col += i;
+ if ((edit_options.visible_tabs || (edit_options.visible_tws && q >= tws))
+ && enable_show_tabs_tws && tty_use_colors ())
+ {
+ if ((p->style & MOD_MARKED) != 0)
+ c = p->style;
+ else if (book_mark != 0)
+ c |= book_mark << 16;
+ else
+ c = p->style | MOD_WHITESPACE;
+ if (i > 2)
+ {
+ p->ch = '<';
+ p->style = c;
+ p++;
+ while (--i > 1)
+ {
+ p->ch = '-';
+ p->style = c;
+ p++;
+ }
+ p->ch = '>';
+ p->style = c;
+ p++;
+ }
+ else if (i > 1)
+ {
+ p->ch = '<';
+ p->style = c;
+ p++;
+ p->ch = '>';
+ p->style = c;
+ p++;
+ }
+ else
+ {
+ p->ch = '>';
+ p->style = c;
+ p++;
+ }
+ }
+ else if (edit_options.visible_tws && q >= tws && enable_show_tabs_tws
+ && tty_use_colors ())
+ {
+ p->ch = '.';
+ p->style |= MOD_WHITESPACE;
+ c = p->style & ~MOD_CURSOR;
+ p++;
+ while (--i != 0)
+ {
+ p->ch = ' ';
+ p->style = c;
+ p++;
+ }
+ }
+ else
+ {
+ p->ch |= ' ';
+ c = p->style & ~MOD_CURSOR;
+ p++;
+ while (--i != 0)
+ {
+ p->ch = ' ';
+ p->style = c;
+ p++;
+ }
+ }
+ }
+ break;
+
+ case ' ':
+ if (edit_options.visible_tws && q >= tws && enable_show_tabs_tws
+ && tty_use_colors ())
+ {
+ p->ch = '.';
+ p->style |= MOD_WHITESPACE;
+ p++;
+ col++;
+ break;
+ }
+ MC_FALLTHROUGH;
+
+ default:
+#ifdef HAVE_CHARSET
+ if (mc_global.utf8_display)
+ {
+ if (!edit->utf8)
+ c = convert_from_8bit_to_utf_c ((unsigned char) c, edit->converter);
+ else if (g_unichar_iswide (c))
+ {
+ wide_width_char = TRUE;
+ col++;
+ }
+ }
+ else if (edit->utf8)
+ c = convert_from_utf_to_current_c (c, edit->converter);
+ else
+ c = convert_to_display_c (c);
+#endif
+
+ /* Caret notation for control characters */
+ if (c < 32)
+ {
+ p->ch = '^';
+ p->style = abn_style;
+ p++;
+ p->ch = c + 0x40;
+ p->style = abn_style;
+ p++;
+ col += 2;
+ control_char = TRUE;
+ break;
+ }
+ if (c == 127)
+ {
+ p->ch = '^';
+ p->style = abn_style;
+ p++;
+ p->ch = '?';
+ p->style = abn_style;
+ p++;
+ col += 2;
+ control_char = TRUE;
+ break;
+ }
+#ifdef HAVE_CHARSET
+ if (edit->utf8)
+ {
+ if (g_unichar_isprint (c))
+ p->ch = c;
+ else
+ {
+ p->ch = '.';
+ p->style = abn_style;
+ }
+ p++;
+ }
+ else
+#endif
+ {
+ if ((mc_global.utf8_display && g_unichar_isprint (c)) ||
+ (!mc_global.utf8_display && is_printable (c)))
+ {
+ p->ch = c;
+ p++;
+ }
+ else
+ {
+ p->ch = '.';
+ p->style = abn_style;
+ p++;
+ }
+ }
+ col++;
+ break;
+ } /* case */
+
+ q++;
+ if (char_length > 1)
+ q += char_length - 1;
+
+ if (col > (end_col - edit->start_col + 1))
+ {
+ if (wide_width_char)
+ {
+ p--;
+ break;
+ }
+ if (control_char)
+ {
+ p -= 2;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ p->ch = 0;
+
+ print_to_widget (edit, row, start_col, start_col_real, end_col, line, line_stat, book_mark);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_draw_this_char (WEdit * edit, off_t curs, long row, long start_column, long end_column)
+{
+ off_t b;
+
+ b = edit_buffer_get_bol (&edit->buffer, curs);
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** cursor must be in screen for other than REDRAW_PAGE passed in force */
+
+static inline void
+render_edit_text (WEdit * edit, long start_row, long start_column, long end_row, long end_column)
+{
+ static long prev_curs_row = 0;
+ static off_t prev_curs = 0;
+
+ Widget *we = WIDGET (edit);
+ Widget *wh = WIDGET (we->owner);
+ WRect *w = &we->rect;
+
+ int force = edit->force;
+ int y1, x1, y2, x2;
+ int last_line, last_column;
+
+ /* draw only visible region */
+
+ last_line = wh->rect.y + wh->rect.lines - 1;
+
+ y1 = w->y;
+ if (y1 > last_line - 1 /* buttonbar */ )
+ return;
+
+ last_column = wh->rect.x + wh->rect.cols - 1;
+
+ x1 = w->x;
+ if (x1 > last_column)
+ return;
+
+ y2 = w->y + w->lines - 1;
+ if (y2 < wh->rect.y + 1 /* menubar */ )
+ return;
+
+ x2 = w->x + w->cols - 1;
+ if (x2 < wh->rect.x)
+ return;
+
+ if ((force & REDRAW_IN_BOUNDS) == 0)
+ {
+ /* !REDRAW_IN_BOUNDS means to ignore bounds and redraw whole rows */
+ /* draw only visible region */
+
+ if (y2 <= last_line - 1 /* buttonbar */ )
+ end_row = w->lines - 1;
+ else if (y1 >= wh->rect.y + 1 /* menubar */ )
+ end_row = wh->rect.lines - 1 - y1 - 1;
+ else
+ end_row = start_row + wh->rect.lines - 1 - 1;
+
+ if (x2 <= last_column)
+ end_column = w->cols - 1;
+ else if (x1 >= wh->rect.x)
+ end_column = wh->rect.cols - 1 - x1;
+ else
+ end_column = start_column + wh->rect.cols - 1;
+ }
+
+ /*
+ * If the position of the page has not moved then we can draw the cursor
+ * character only. This will prevent line flicker when using arrow keys.
+ */
+ if ((force & REDRAW_CHAR_ONLY) == 0 || (force & REDRAW_PAGE) != 0)
+ {
+ long row = 0;
+ long b;
+
+ if ((force & REDRAW_PAGE) != 0)
+ {
+ b = edit_buffer_get_forward_offset (&edit->buffer, edit->start_display, start_row, 0);
+ for (row = start_row; row <= end_row; row++)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+ b = edit_buffer_get_forward_offset (&edit->buffer, b, 1, 0);
+ }
+ }
+ else
+ {
+ long curs_row = edit->curs_row;
+
+ if ((force & REDRAW_BEFORE_CURSOR) != 0 && start_row < curs_row)
+ {
+ long upto;
+
+ b = edit->start_display;
+ upto = MIN (curs_row - 1, end_row);
+ for (row = start_row; row <= upto; row++)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+ b = edit_buffer_get_forward_offset (&edit->buffer, b, 1, 0);
+ }
+ }
+
+ /* if (force & REDRAW_LINE) ---> default */
+ b = edit_buffer_get_current_bol (&edit->buffer);
+ if (curs_row >= start_row && curs_row <= end_row)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, curs_row, start_column, end_column);
+ }
+
+ if ((force & REDRAW_AFTER_CURSOR) != 0 && end_row > curs_row)
+ {
+ b = edit_buffer_get_forward_offset (&edit->buffer, b, 1, 0);
+ for (row = MAX (curs_row + 1, start_row); row <= end_row; row++)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+ b = edit_buffer_get_forward_offset (&edit->buffer, b, 1, 0);
+ }
+ }
+
+ if ((force & REDRAW_LINE_ABOVE) != 0 && curs_row >= 1)
+ {
+ row = curs_row - 1;
+ b = edit_buffer_get_backward_offset (&edit->buffer,
+ edit_buffer_get_current_bol (&edit->buffer),
+ 1);
+ if (row >= start_row && row <= end_row)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+ }
+ }
+
+ if ((force & REDRAW_LINE_BELOW) != 0 && row < w->lines - 1)
+ {
+ row = curs_row + 1;
+ b = edit_buffer_get_current_bol (&edit->buffer);
+ b = edit_buffer_get_forward_offset (&edit->buffer, b, 1, 0);
+ if (row >= start_row && row <= end_row)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+ }
+ }
+ }
+ }
+ else if (prev_curs_row < edit->curs_row)
+ {
+ /* with the new text highlighting, we must draw from the top down */
+ edit_draw_this_char (edit, prev_curs, prev_curs_row, start_column, end_column);
+ edit_draw_this_char (edit, edit->buffer.curs1, edit->curs_row, start_column, end_column);
+ }
+ else
+ {
+ edit_draw_this_char (edit, edit->buffer.curs1, edit->curs_row, start_column, end_column);
+ edit_draw_this_char (edit, prev_curs, prev_curs_row, start_column, end_column);
+ }
+
+ edit->force = 0;
+
+ prev_curs_row = edit->curs_row;
+ prev_curs = edit->buffer.curs1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_render (WEdit * edit, int page, int row_start, int col_start, int row_end, int col_end)
+{
+ if (page != 0) /* if it was an expose event, 'page' would be set */
+ edit->force |= REDRAW_PAGE | REDRAW_IN_BOUNDS;
+
+ render_edit_text (edit, row_start, col_start, row_end, col_end);
+
+ /*
+ * edit->force != 0 means a key was pending and the redraw
+ * was halted, so next time we must redraw everything in case stuff
+ * was left undrawn from a previous key press.
+ */
+ if (edit->force != 0)
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_status (WEdit * edit, gboolean active)
+{
+ int color;
+
+ if (edit->fullscreen)
+ {
+ color = STATUSBAR_COLOR;
+ edit_status_fullscreen (edit, color);
+ }
+ else
+ {
+ color = edit->drag_state != MCEDIT_DRAG_NONE ? EDITOR_FRAME_DRAG : active ?
+ EDITOR_FRAME_ACTIVE : EDITOR_FRAME;
+ edit_draw_frame (edit, color, active);
+ edit_status_window (edit);
+ }
+
+ edit_draw_window_icons (edit, color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** this scrolls the text so that cursor is on the screen */
+void
+edit_scroll_screen_over_cursor (WEdit * edit)
+{
+ WRect *w = &WIDGET (edit)->rect;
+
+ long p;
+ long outby;
+ int b_extreme, t_extreme, l_extreme, r_extreme;
+
+ if (w->lines <= 0 || w->cols <= 0)
+ return;
+
+ rect_resize (w, -EDIT_TEXT_VERTICAL_OFFSET,
+ -(EDIT_TEXT_HORIZONTAL_OFFSET + edit_options.line_state_width));
+
+ if (!edit->fullscreen)
+ rect_grow (w, -1, -1);
+
+ r_extreme = EDIT_RIGHT_EXTREME;
+ l_extreme = EDIT_LEFT_EXTREME;
+ b_extreme = EDIT_BOTTOM_EXTREME;
+ t_extreme = EDIT_TOP_EXTREME;
+ if (edit->found_len != 0)
+ {
+ b_extreme = MAX (w->lines / 4, b_extreme);
+ t_extreme = MAX (w->lines / 4, t_extreme);
+ }
+ if (b_extreme + t_extreme + 1 > w->lines)
+ {
+ int n;
+
+ n = b_extreme + t_extreme;
+ if (n == 0)
+ n = 1;
+ b_extreme = (b_extreme * (w->lines - 1)) / n;
+ t_extreme = (t_extreme * (w->lines - 1)) / n;
+ }
+ if (l_extreme + r_extreme + 1 > w->cols)
+ {
+ int n;
+
+ n = l_extreme + r_extreme;
+ if (n == 0)
+ n = 1;
+ l_extreme = (l_extreme * (w->cols - 1)) / n;
+ r_extreme = (r_extreme * (w->cols - 1)) / n;
+ }
+ p = edit_get_col (edit) + edit->over_col;
+ edit_update_curs_row (edit);
+ outby = p + edit->start_col - w->cols + 1 + (r_extreme + edit->found_len);
+ if (outby > 0)
+ edit_scroll_right (edit, outby);
+ outby = l_extreme - p - edit->start_col;
+ if (outby > 0)
+ edit_scroll_left (edit, outby);
+ p = edit->curs_row;
+ outby = p - w->lines + 1 + b_extreme;
+ if (outby > 0)
+ edit_scroll_downward (edit, outby);
+ outby = t_extreme - p;
+ if (outby > 0)
+ edit_scroll_upward (edit, outby);
+ edit_update_curs_row (edit);
+
+ rect_resize (w, EDIT_TEXT_VERTICAL_OFFSET,
+ EDIT_TEXT_HORIZONTAL_OFFSET + edit_options.line_state_width);
+ if (!edit->fullscreen)
+ rect_grow (w, 1, 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_render_keypress (WEdit * edit)
+{
+ edit_render (edit, 0, 0, 0, 0, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editmacros.c b/src/editor/editmacros.c
new file mode 100644
index 0000000..8545d67
--- /dev/null
+++ b/src/editor/editmacros.c
@@ -0,0 +1,437 @@
+/*
+ Editor macros engine
+
+ Copyright (C) 2001-2023
+ Free Software Foundation, Inc.
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/mcconfig.h"
+#include "lib/tty/key.h" /* tty_keyname_to_keycode*() */
+#include "lib/keybind.h" /* keybind_lookup_actionname() */
+#include "lib/fileloc.h"
+
+#include "src/setup.h" /* macro_action_t */
+#include "src/history.h" /* MC_HISTORY_EDIT_REPEAT */
+
+#include "editwidget.h"
+
+#include "editmacros.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+edit_macro_comparator (gconstpointer * macro1, gconstpointer * macro2)
+{
+ const macros_t *m1 = (const macros_t *) macro1;
+ const macros_t *m2 = (const macros_t *) macro2;
+
+ return m1->hotkey - m2->hotkey;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_macro_sort_by_hotkey (void)
+{
+ if (macros_list != NULL && macros_list->len != 0)
+ g_array_sort (macros_list, (GCompareFunc) edit_macro_comparator);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+edit_get_macro (WEdit * edit, int hotkey)
+{
+ macros_t *array_start;
+ macros_t *result;
+ macros_t search_macro = {
+ .hotkey = hotkey
+ };
+
+ (void) edit;
+
+ result = bsearch (&search_macro, macros_list->data, macros_list->len,
+ sizeof (macros_t), (GCompareFunc) edit_macro_comparator);
+
+ if (result == NULL || result->macro == NULL)
+ return (-1);
+
+ array_start = &g_array_index (macros_list, struct macros_t, 0);
+
+ return (int) (result - array_start);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns FALSE on error */
+static gboolean
+edit_delete_macro (WEdit * edit, int hotkey)
+{
+ mc_config_t *macros_config = NULL;
+ const char *section_name = "editor";
+ gchar *macros_fname;
+ int indx;
+ char *skeyname;
+
+ /* clear array of actions for current hotkey */
+ while ((indx = edit_get_macro (edit, hotkey)) != -1)
+ {
+ macros_t *macros;
+
+ macros = &g_array_index (macros_list, struct macros_t, indx);
+ g_array_free (macros->macro, TRUE);
+ g_array_remove_index (macros_list, indx);
+ }
+
+ macros_fname = mc_config_get_full_path (MC_MACRO_FILE);
+ macros_config = mc_config_init (macros_fname, FALSE);
+ g_free (macros_fname);
+
+ if (macros_config == NULL)
+ return FALSE;
+
+ skeyname = tty_keycode_to_keyname (hotkey);
+ while (mc_config_del_key (macros_config, section_name, skeyname))
+ ;
+ g_free (skeyname);
+ mc_config_save_file (macros_config, NULL);
+ mc_config_deinit (macros_config);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns FALSE on error */
+gboolean
+edit_store_macro_cmd (WEdit * edit)
+{
+ int i;
+ int hotkey;
+ GString *macros_string = NULL;
+ const char *section_name = "editor";
+ gchar *macros_fname;
+ GArray *macros = NULL;
+ int tmp_act;
+ mc_config_t *macros_config;
+ char *skeyname;
+
+ hotkey =
+ editcmd_dialog_raw_key_query (_("Save macro"), _("Press the macro's new hotkey:"), TRUE);
+ if (hotkey == ESC_CHAR)
+ return FALSE;
+
+ tmp_act = keybind_lookup_keymap_command (WIDGET (edit)->keymap, hotkey);
+ /* return FALSE if try assign macro into restricted hotkeys */
+ if (tmp_act == CK_MacroStartRecord
+ || tmp_act == CK_MacroStopRecord || tmp_act == CK_MacroStartStopRecord)
+ return FALSE;
+
+ edit_delete_macro (edit, hotkey);
+
+ macros_fname = mc_config_get_full_path (MC_MACRO_FILE);
+ macros_config = mc_config_init (macros_fname, FALSE);
+ g_free (macros_fname);
+
+ if (macros_config == NULL)
+ return FALSE;
+
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ skeyname = tty_keycode_to_keyname (hotkey);
+
+ for (i = 0; i < macro_index; i++)
+ {
+ macro_action_t m_act;
+ const char *action_name;
+
+ action_name = keybind_lookup_actionname (record_macro_buf[i].action);
+ if (action_name == NULL)
+ break;
+
+ if (macros == NULL)
+ {
+ macros = g_array_new (TRUE, FALSE, sizeof (macro_action_t));
+ macros_string = g_string_sized_new (250);
+ }
+
+ m_act.action = record_macro_buf[i].action;
+ m_act.ch = record_macro_buf[i].ch;
+ g_array_append_val (macros, m_act);
+ g_string_append_printf (macros_string, "%s:%i;", action_name, (int) record_macro_buf[i].ch);
+ }
+
+ if (macros == NULL)
+ mc_config_del_key (macros_config, section_name, skeyname);
+ else
+ {
+ macros_t macro;
+
+ macro.hotkey = hotkey;
+ macro.macro = macros;
+ g_array_append_val (macros_list, macro);
+ mc_config_set_string (macros_config, section_name, skeyname, macros_string->str);
+ }
+
+ g_free (skeyname);
+
+ edit_macro_sort_by_hotkey ();
+
+ if (macros_string != NULL)
+ g_string_free (macros_string, TRUE);
+ mc_config_save_file (macros_config, NULL);
+ mc_config_deinit (macros_config);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** return FALSE on error */
+
+gboolean
+edit_load_macro_cmd (WEdit * edit)
+{
+ mc_config_t *macros_config = NULL;
+ gchar **profile_keys, **keys;
+ gchar **values, **curr_values;
+ const char *section_name = "editor";
+ gchar *macros_fname;
+
+ (void) edit;
+
+ if (macros_list == NULL || macros_list->len != 0)
+ return FALSE;
+
+ macros_fname = mc_config_get_full_path (MC_MACRO_FILE);
+ macros_config = mc_config_init (macros_fname, TRUE);
+ g_free (macros_fname);
+
+ if (macros_config == NULL)
+ return FALSE;
+
+ keys = mc_config_get_keys (macros_config, section_name, NULL);
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ {
+ int hotkey;
+ GArray *macros = NULL;
+
+ values = mc_config_get_string_list (macros_config, section_name, *profile_keys, NULL);
+ hotkey = tty_keyname_to_keycode (*profile_keys, NULL);
+
+ for (curr_values = values; *curr_values != NULL && *curr_values[0] != '\0'; curr_values++)
+ {
+ char **macro_pair;
+
+ macro_pair = g_strsplit (*curr_values, ":", 2);
+
+ if (macro_pair != NULL)
+ {
+ macro_action_t m_act = {
+ .action = 0,
+ .ch = -1
+ };
+
+ if (macro_pair[0] != NULL && macro_pair[0][0] != '\0')
+ m_act.action = keybind_lookup_action (macro_pair[0]);
+
+ if (macro_pair[1] != NULL && macro_pair[1][0] != '\0')
+ m_act.ch = strtol (macro_pair[1], NULL, 0);
+
+ if (m_act.action != 0)
+ {
+ /* a shell command */
+ if ((m_act.action / CK_PipeBlock (0)) == 1)
+ {
+ m_act.action = CK_PipeBlock (0);
+ if (m_act.ch > 0)
+ m_act.action += m_act.ch;
+ m_act.ch = -1;
+ }
+
+ if (macros == NULL)
+ macros = g_array_new (TRUE, FALSE, sizeof (m_act));
+
+ g_array_append_val (macros, m_act);
+ }
+
+ g_strfreev (macro_pair);
+ }
+ }
+
+ if (macros != NULL)
+ {
+ macros_t macro = {
+ .hotkey = hotkey,
+ .macro = macros
+ };
+
+ g_array_append_val (macros_list, macro);
+ }
+
+ g_strfreev (values);
+ }
+
+ g_strfreev (keys);
+ mc_config_deinit (macros_config);
+ edit_macro_sort_by_hotkey ();
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_delete_macro_cmd (WEdit * edit)
+{
+ int hotkey;
+
+ hotkey = editcmd_dialog_raw_key_query (_("Delete macro"), _("Press macro hotkey:"), TRUE);
+
+ if (hotkey != 0 && !edit_delete_macro (edit, hotkey))
+ message (D_ERROR, _("Delete macro"), _("Macro not deleted"));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_repeat_macro_cmd (WEdit * edit)
+{
+ gboolean ok;
+ char *f;
+ long count_repeat = 0;
+
+ f = input_dialog (_("Repeat last commands"), _("Repeat times:"), MC_HISTORY_EDIT_REPEAT, NULL,
+ INPUT_COMPLETE_NONE);
+ ok = (f != NULL && *f != '\0');
+
+ if (ok)
+ {
+ char *error = NULL;
+
+ count_repeat = strtol (f, &error, 0);
+
+ ok = (*error == '\0');
+ }
+
+ g_free (f);
+
+ if (ok)
+ {
+ int i, j;
+
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+ edit->force |= REDRAW_PAGE;
+
+ for (j = 0; j < count_repeat; j++)
+ for (i = 0; i < macro_index; i++)
+ edit_execute_cmd (edit, record_macro_buf[i].action, record_macro_buf[i].ch);
+
+ edit_update_screen (edit);
+ }
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns FALSE on error */
+gboolean
+edit_execute_macro (WEdit * edit, int hotkey)
+{
+ gboolean res = FALSE;
+
+ if (hotkey != 0)
+ {
+ int indx;
+
+ indx = edit_get_macro (edit, hotkey);
+ if (indx != -1)
+ {
+ const macros_t *macros;
+
+ macros = &g_array_index (macros_list, struct macros_t, indx);
+ if (macros->macro->len != 0)
+ {
+ guint i;
+
+ edit->force |= REDRAW_PAGE;
+
+ for (i = 0; i < macros->macro->len; i++)
+ {
+ const macro_action_t *m_act;
+
+ m_act = &g_array_index (macros->macro, struct macro_action_t, i);
+ edit_execute_cmd (edit, m_act->action, m_act->ch);
+ res = TRUE;
+ }
+ }
+ }
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_begin_end_macro_cmd (WEdit * edit)
+{
+ /* edit is a pointer to the widget */
+ if (edit != NULL)
+ {
+ long command = macro_index < 0 ? CK_MacroStartRecord : CK_MacroStopRecord;
+
+ edit_execute_key_command (edit, command, -1);
+ }
+}
+
+ /* --------------------------------------------------------------------------------------------- */
+
+void
+edit_begin_end_repeat_cmd (WEdit * edit)
+{
+ /* edit is a pointer to the widget */
+ if (edit != NULL)
+ {
+ long command = macro_index < 0 ? CK_RepeatStartRecord : CK_RepeatStopRecord;
+
+ edit_execute_key_command (edit, command, -1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editmacros.h b/src/editor/editmacros.h
new file mode 100644
index 0000000..5d234e0
--- /dev/null
+++ b/src/editor/editmacros.h
@@ -0,0 +1,24 @@
+#ifndef MC__EDIT_MACROS_H
+#define MC__EDIT_MACROS_H 1
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+int edit_store_macro_cmd (WEdit * edit);
+gboolean edit_load_macro_cmd (WEdit * edit);
+void edit_delete_macro_cmd (WEdit * edit);
+gboolean edit_repeat_macro_cmd (WEdit * edit);
+gboolean edit_execute_macro (WEdit * edit, int hotkey);
+void edit_begin_end_macro_cmd (WEdit * edit);
+void edit_begin_end_repeat_cmd (WEdit * edit);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EDIT_MACROS_H */
diff --git a/src/editor/editmenu.c b/src/editor/editmenu.c
new file mode 100644
index 0000000..3509fa2
--- /dev/null
+++ b/src/editor/editmenu.c
@@ -0,0 +1,338 @@
+/*
+ Editor menu definitions and initialisation
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru> 2012
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: editor menu definitions and initialisation
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* KEY_F */
+#include "lib/tty/key.h" /* XCTRL */
+#include "lib/widget.h"
+
+#include "src/setup.h" /* drop_menus */
+#include "src/keymap.h"
+
+#include "edit-impl.h"
+#include "editwidget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_file_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Open file..."), CK_EditFile));
+ entries = g_list_prepend (entries, menu_entry_new (_("&New"), CK_EditNew));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Close"), CK_Close));
+ entries = g_list_prepend (entries, menu_entry_new (_("&History..."), CK_History));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Save"), CK_Save));
+ entries = g_list_prepend (entries, menu_entry_new (_("Save &as..."), CK_SaveAs));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Insert file..."), CK_InsertFile));
+ entries = g_list_prepend (entries, menu_entry_new (_("Cop&y to file..."), CK_BlockSave));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&User menu..."), CK_UserMenu));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("A&bout..."), CK_About));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Quit"), CK_Quit));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_edit_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Undo"), CK_Undo));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Redo"), CK_Redo));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Toggle ins/overw"), CK_InsertOverwrite));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("To&ggle mark"), CK_Mark));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Mark columns"), CK_MarkColumn));
+ entries = g_list_prepend (entries, menu_entry_new (_("Mark &all"), CK_MarkAll));
+ entries = g_list_prepend (entries, menu_entry_new (_("Unmar&k"), CK_Unmark));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("Cop&y"), CK_Copy));
+ entries = g_list_prepend (entries, menu_entry_new (_("Mo&ve"), CK_Move));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Delete"), CK_Remove));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("Co&py to clipfile"), CK_Store));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Cut to clipfile"), CK_Cut));
+ entries = g_list_prepend (entries, menu_entry_new (_("Pa&ste from clipfile"), CK_Paste));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Beginning"), CK_Top));
+ entries = g_list_prepend (entries, menu_entry_new (_("&End"), CK_Bottom));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_search_replace_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Search..."), CK_Search));
+ entries = g_list_prepend (entries, menu_entry_new (_("Search &again"), CK_SearchContinue));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Replace..."), CK_Replace));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Toggle bookmark"), CK_Bookmark));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Next bookmark"), CK_BookmarkNext));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Prev bookmark"), CK_BookmarkPrev));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Flush bookmarks"), CK_BookmarkFlush));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_command_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Go to line..."), CK_Goto));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Toggle line state"), CK_ShowNumbers));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("Go to matching &bracket"), CK_MatchBracket));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("Toggle s&yntax highlighting"), CK_SyntaxOnOff));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Find declaration"), CK_Find));
+ entries = g_list_prepend (entries, menu_entry_new (_("Back from &declaration"), CK_FilePrev));
+ entries = g_list_prepend (entries, menu_entry_new (_("For&ward to declaration"), CK_FileNext));
+#ifdef HAVE_CHARSET
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("Encod&ing..."), CK_SelectCodepage));
+#endif
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Refresh screen"), CK_Refresh));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries =
+ g_list_prepend (entries,
+ menu_entry_new (_("&Start/Stop record macro"), CK_MacroStartStopRecord));
+ entries = g_list_prepend (entries, menu_entry_new (_("Delete macr&o..."), CK_MacroDelete));
+ entries =
+ g_list_prepend (entries,
+ menu_entry_new (_("Record/Repeat &actions"), CK_RepeatStartStopRecord));
+ entries = g_list_prepend (entries, menu_separator_new ());
+#ifdef HAVE_ASPELL
+ if (strcmp (spell_language, "NONE") != 0)
+ {
+ entries = g_list_prepend (entries, menu_entry_new (_("S&pell check"), CK_SpellCheck));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("C&heck word"), CK_SpellCheckCurrentWord));
+ entries =
+ g_list_prepend (entries,
+ menu_entry_new (_("Change spelling &language..."),
+ CK_SpellCheckSelectLang));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ }
+#endif /* HAVE_ASPELL */
+ entries = g_list_prepend (entries, menu_entry_new (_("&Mail..."), CK_EditMail));
+
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_format_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("Insert &literal..."), CK_InsertLiteral));
+ entries = g_list_prepend (entries, menu_entry_new (_("Insert &date/time"), CK_Date));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Format paragraph"), CK_ParagraphFormat));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Sort..."), CK_Sort));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("&Paste output of..."), CK_ExternalCommand));
+ entries = g_list_prepend (entries, menu_entry_new (_("&External formatter"), CK_PipeBlock (0)));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create the 'window' popup menu
+ */
+
+static GList *
+create_window_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Move"), CK_WindowMove));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Resize"), CK_WindowResize));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("&Toggle fullscreen"), CK_WindowFullscreen));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Next"), CK_WindowNext));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Previous"), CK_WindowPrev));
+ entries = g_list_prepend (entries, menu_entry_new (_("&List..."), CK_WindowList));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_options_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&General..."), CK_Options));
+ entries = g_list_prepend (entries, menu_entry_new (_("Save &mode..."), CK_OptionsSaveMode));
+ entries = g_list_prepend (entries, menu_entry_new (_("Learn &keys..."), CK_LearnKeys));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("Syntax &highlighting..."), CK_SyntaxChoose));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("S&yntax file"), CK_EditSyntaxFile));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Menu file"), CK_EditUserMenu));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Save setup"), CK_SaveSetup));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_drop_menu_cmd (WDialog * h, int which)
+{
+ WMenuBar *menubar;
+
+ menubar = menubar_find (h);
+ menubar_activate (menubar, drop_menus, which);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_init_menu (WMenuBar * menubar)
+{
+ menubar_add_menu (menubar,
+ menu_new (_("&File"), create_file_menu (), "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("&Edit"), create_edit_menu (), "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("&Search"), create_search_replace_menu (),
+ "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("&Command"), create_command_menu (), "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("For&mat"), create_format_menu (), "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("&Window"), create_window_menu (), "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("&Options"), create_options_menu (), "[Internal File Editor]"));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_menu_cmd (WDialog * h)
+{
+ edit_drop_menu_cmd (h, -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_drop_hotkey_menu (WDialog * h, int key)
+{
+ int m = 0;
+ switch (key)
+ {
+ case ALT ('f'):
+ m = 0;
+ break;
+ case ALT ('e'):
+ m = 1;
+ break;
+ case ALT ('s'):
+ m = 2;
+ break;
+ case ALT ('c'):
+ m = 3;
+ break;
+ case ALT ('m'):
+ m = 4;
+ break;
+ case ALT ('w'):
+ m = 5;
+ break;
+ case ALT ('o'):
+ m = 6;
+ break;
+ default:
+ return FALSE;
+ }
+
+ edit_drop_menu_cmd (h, m);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editoptions.c b/src/editor/editoptions.c
new file mode 100644
index 0000000..9e059f3
--- /dev/null
+++ b/src/editor/editoptions.c
@@ -0,0 +1,241 @@
+/*
+ Editor options dialog box
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2012-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: editor options dialog box
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+
+#include <stdlib.h> /* atoi(), NULL */
+
+#include "lib/global.h"
+#include "lib/widget.h"
+
+#include "editwidget.h"
+#include "edit-impl.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const char *wrap_str[] = {
+ N_("&None"),
+ N_("&Dynamic paragraphing"),
+ N_("Type &writer wrap"),
+ NULL
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_NLS
+static void
+i18n_translate_array (const char *array[])
+{
+ while (*array != NULL)
+ {
+ *array = _(*array);
+ array++;
+ }
+}
+#endif /* ENABLE_NLS */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for the iteration of objects in the 'editors' array.
+ * Tear down 'over_col' property in all editors.
+ *
+ * @param data probably WEdit object
+ * @param user_data unused
+ */
+
+static void
+edit_reset_over_col (void *data, void *user_data)
+{
+ (void) user_data;
+
+ if (edit_widget_is_editor (CONST_WIDGET (data)))
+ EDIT (data)->over_col = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for the iteration of objects in the 'editors' array.
+ * Reload syntax lighlighting in all editors.
+ *
+ * @param data probably WEdit object
+ * @param user_data unused
+ */
+
+static void
+edit_reload_syntax (void *data, void *user_data)
+{
+ (void) user_data;
+
+ if (edit_widget_is_editor (CONST_WIDGET (data)))
+ {
+ WEdit *edit = EDIT (data);
+
+ edit_load_syntax (edit, NULL, edit->syntax_type);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_options_dialog (WDialog * h)
+{
+ char wrap_length[16], tab_spacing[16];
+ char *p, *q;
+ int wrap_mode = 0;
+ gboolean old_syntax_hl;
+
+#ifdef ENABLE_NLS
+ static gboolean i18n_flag = FALSE;
+
+ if (!i18n_flag)
+ {
+ i18n_translate_array (wrap_str);
+ i18n_flag = TRUE;
+ }
+#endif /* ENABLE_NLS */
+
+ g_snprintf (wrap_length, sizeof (wrap_length), "%d", edit_options.word_wrap_line_length);
+ g_snprintf (tab_spacing, sizeof (tab_spacing), "%d", TAB_SIZE);
+
+ if (edit_options.auto_para_formatting)
+ wrap_mode = 1;
+ else if (edit_options.typewriter_wrap)
+ wrap_mode = 2;
+ else
+ wrap_mode = 0;
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_START_GROUPBOX (N_("Wrap mode")),
+ QUICK_RADIO (3, wrap_str, &wrap_mode, NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_SEPARATOR (FALSE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_START_GROUPBOX (N_("Tabulation")),
+ QUICK_CHECKBOX (N_("&Fake half tabs"), &edit_options.fake_half_tabs, NULL),
+ QUICK_CHECKBOX (N_("&Backspace through tabs"),
+ &edit_options.backspace_through_tabs, NULL),
+ QUICK_CHECKBOX (N_("Fill tabs with &spaces"),
+ &edit_options.fill_tabs_with_spaces, NULL),
+ QUICK_LABELED_INPUT (N_("Tab spacing:"), input_label_left, tab_spacing,
+ "edit-tab-spacing", &q, NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_STOP_GROUPBOX,
+ QUICK_NEXT_COLUMN,
+ QUICK_START_GROUPBOX (N_("Other options")),
+ QUICK_CHECKBOX (N_("&Return does autoindent"), &edit_options.return_does_auto_indent, NULL),
+ QUICK_CHECKBOX (N_("Confir&m before saving"), &edit_options.confirm_save, NULL),
+ QUICK_CHECKBOX (N_("Save file &position"), &edit_options.save_position, NULL),
+ QUICK_CHECKBOX (N_("&Visible trailing spaces"), &edit_options.visible_tws, NULL),
+ QUICK_CHECKBOX (N_("Visible &tabs"), &edit_options.visible_tabs, NULL),
+ QUICK_CHECKBOX (N_("Synta&x highlighting"), &edit_options.syntax_highlighting, NULL),
+ QUICK_CHECKBOX (N_("C&ursor after inserted block"),
+ &edit_options.cursor_after_inserted_block, NULL),
+ QUICK_CHECKBOX (N_("Pers&istent selection"), &edit_options.persistent_selections, NULL),
+ QUICK_CHECKBOX (N_("Cursor be&yond end of line"), &edit_options.cursor_beyond_eol, NULL),
+ QUICK_CHECKBOX (N_("&Group undo"), &edit_options.group_undo, NULL),
+ QUICK_LABELED_INPUT (N_("Word wrap line length:"), input_label_left, wrap_length,
+ "edit-word-wrap", &p, NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_STOP_GROUPBOX,
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 74 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Editor options"), "[Editor options]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) == B_CANCEL)
+ return;
+ }
+
+ old_syntax_hl = edit_options.syntax_highlighting;
+
+ if (!edit_options.cursor_beyond_eol)
+ g_list_foreach (GROUP (h)->widgets, edit_reset_over_col, NULL);
+
+ if (*p != '\0')
+ {
+ edit_options.word_wrap_line_length = atoi (p);
+ if (edit_options.word_wrap_line_length <= 0)
+ edit_options.word_wrap_line_length = DEFAULT_WRAP_LINE_LENGTH;
+ g_free (p);
+ }
+
+ if (*q != '\0')
+ {
+ TAB_SIZE = atoi (q);
+ if (TAB_SIZE <= 0)
+ TAB_SIZE = DEFAULT_TAB_SPACING;
+ g_free (q);
+ }
+
+ if (wrap_mode == 1)
+ {
+ edit_options.auto_para_formatting = TRUE;
+ edit_options.typewriter_wrap = FALSE;
+ }
+ else if (wrap_mode == 2)
+ {
+ edit_options.auto_para_formatting = FALSE;
+ edit_options.typewriter_wrap = TRUE;
+ }
+ else
+ {
+ edit_options.auto_para_formatting = FALSE;
+ edit_options.typewriter_wrap = FALSE;
+ }
+
+ /* Load or unload syntax rules if the option has changed */
+ if (edit_options.syntax_highlighting != old_syntax_hl)
+ g_list_foreach (GROUP (h)->widgets, edit_reload_syntax, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editsearch.c b/src/editor/editsearch.c
new file mode 100644
index 0000000..1bdf883
--- /dev/null
+++ b/src/editor/editsearch.c
@@ -0,0 +1,1042 @@
+/*
+ Search & replace engine of MCEditor.
+
+ Copyright (C) 2021-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2021-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+
+#include "lib/global.h"
+#include "lib/search.h"
+#include "lib/mcconfig.h" /* mc_config_history_get */
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* cp_source */
+#endif
+#include "lib/util.h"
+#include "lib/widget.h"
+#include "lib/skin.h" /* BOOK_MARK_FOUND_COLOR */
+
+#include "src/history.h" /* MC_HISTORY_SHARED_SEARCH */
+#include "src/setup.h" /* verbose */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+
+#include "editsearch.h"
+
+/*** global variables ****************************************************************************/
+
+edit_search_options_t edit_search_options = {
+ .type = MC_SEARCH_T_NORMAL,
+ .case_sens = FALSE,
+ .backwards = FALSE,
+ .only_in_selection = FALSE,
+ .whole_words = FALSE,
+ .all_codepages = FALSE
+};
+
+/*** file scope macro definitions ****************************************************************/
+
+#define B_REPLACE_ALL (B_USER+1)
+#define B_REPLACE_ONE (B_USER+2)
+#define B_SKIP_REPLACE (B_USER+3)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+edit_dialog_search_show (WEdit * edit)
+{
+ char *search_text;
+ size_t num_of_types = 0;
+ gchar **list_of_types;
+ int dialog_result;
+
+ 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, &search_text, NULL, FALSE, FALSE,
+ INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (num_of_types, (const char **) list_of_types,
+ (int *) &edit_search_options.type, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Cas&e sensitive"), &edit_search_options.case_sens, NULL),
+ QUICK_CHECKBOX (N_("&Backwards"), &edit_search_options.backwards, NULL),
+ QUICK_CHECKBOX (N_("In se&lection"), &edit_search_options.only_in_selection, NULL),
+ QUICK_CHECKBOX (N_("&Whole words"), &edit_search_options.whole_words, NULL),
+#ifdef HAVE_CHARSET
+ QUICK_CHECKBOX (N_("&All charsets"), &edit_search_options.all_codepages, NULL),
+#endif
+ QUICK_STOP_COLUMNS,
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
+ QUICK_BUTTON (N_("&Find all"), B_USER, NULL, NULL),
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 58 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Search"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ dialog_result = quick_dialog (&qdlg);
+ }
+
+ g_strfreev (list_of_types);
+
+ if (dialog_result == B_CANCEL || search_text[0] == '\0')
+ {
+ g_free (search_text);
+ return FALSE;
+ }
+
+ if (dialog_result == B_USER)
+ search_create_bookmark = TRUE;
+
+#ifdef HAVE_CHARSET
+ {
+ GString *tmp;
+
+ tmp = str_convert_to_input (search_text);
+ g_free (search_text);
+ if (tmp != NULL)
+ search_text = g_string_free (tmp, FALSE);
+ else
+ search_text = g_strdup ("");
+ }
+#endif
+
+ edit_search_deinit (edit);
+ edit->last_search_string = search_text;
+
+ return edit_search_init (edit, edit->last_search_string);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_dialog_replace_show (WEdit * edit, const char *search_default, const char *replace_default,
+ /*@out@ */ char **search_text, /*@out@ */ char **replace_text)
+{
+ size_t num_of_types = 0;
+ gchar **list_of_types;
+
+ if ((search_default == NULL) || (*search_default == '\0'))
+ search_default = INPUT_LAST_TEXT;
+
+ 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, search_default,
+ MC_HISTORY_SHARED_SEARCH, search_text, NULL, FALSE, FALSE,
+ INPUT_COMPLETE_NONE),
+ QUICK_LABELED_INPUT (N_("Enter replacement string:"), input_label_above, replace_default,
+ "replace", replace_text, NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (num_of_types, (const char **) list_of_types,
+ (int *) &edit_search_options.type, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Cas&e sensitive"), &edit_search_options.case_sens, NULL),
+ QUICK_CHECKBOX (N_("&Backwards"), &edit_search_options.backwards, NULL),
+ QUICK_CHECKBOX (N_("In se&lection"), &edit_search_options.only_in_selection, NULL),
+ QUICK_CHECKBOX (N_("&Whole words"), &edit_search_options.whole_words, NULL),
+#ifdef HAVE_CHARSET
+ QUICK_CHECKBOX (N_("&All charsets"), &edit_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_("Replace"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ edit->replace_mode = 0;
+ else
+ {
+ *replace_text = NULL;
+ *search_text = NULL;
+ }
+ }
+
+ g_strfreev (list_of_types);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+edit_dialog_replace_prompt_show (WEdit * edit, char *from_text, char *to_text, int xpos, int ypos)
+{
+ Widget *w = WIDGET (edit);
+
+ /* dialog size */
+ int dlg_height = 10;
+ int dlg_width;
+
+ char tmp[BUF_MEDIUM];
+ char *repl_from, *repl_to;
+ int retval;
+
+ if (xpos == -1)
+ xpos = w->rect.x + edit_options.line_state_width + 1;
+ if (ypos == -1)
+ ypos = w->rect.y + w->rect.lines / 2;
+ /* Sometimes menu can hide replaced text. I don't like it */
+ if ((edit->curs_row >= ypos - 1) && (edit->curs_row <= ypos + dlg_height - 1))
+ ypos -= dlg_height;
+
+ dlg_width = WIDGET (w->owner)->rect.cols - xpos - 1;
+
+ g_snprintf (tmp, sizeof (tmp), "\"%s\"", from_text);
+ repl_from = g_strdup (str_trunc (tmp, dlg_width - 7));
+
+ g_snprintf (tmp, sizeof (tmp), "\"%s\"", to_text);
+ repl_to = g_strdup (str_trunc (tmp, dlg_width - 7));
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABEL (repl_from, NULL),
+ QUICK_LABEL (N_("Replace with:"), NULL),
+ QUICK_LABEL (repl_to, NULL),
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&Replace"), B_ENTER, NULL, NULL),
+ QUICK_BUTTON (N_("A&ll"), B_REPLACE_ALL, NULL, NULL),
+ QUICK_BUTTON (N_("&Skip"), B_SKIP_REPLACE, NULL, NULL),
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { ypos, xpos, 0, -1 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Confirm replace"), NULL,
+ quick_widgets, NULL, NULL
+ };
+
+ retval = quick_dialog (&qdlg);
+ }
+
+ g_free (repl_from);
+ g_free (repl_to);
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get EOL symbol for searching.
+ *
+ * @param edit editor object
+ * @return EOL symbol
+ */
+
+static inline char
+edit_search_get_current_end_line_char (const WEdit * edit)
+{
+ switch (edit->lb)
+ {
+ case LB_MAC:
+ return '\r';
+ default:
+ return '\n';
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Checking if search condition have BOL(^) or EOL ($) regexp special characters.
+ *
+ * @param search search object
+ * @return result of checks.
+ */
+
+static edit_search_line_t
+edit_get_search_line_type (mc_search_t * search)
+{
+ edit_search_line_t search_line_type = 0;
+
+ if (search->search_type != MC_SEARCH_T_REGEX)
+ return search_line_type;
+
+ if (search->original.str->str[0] == '^')
+ search_line_type |= AT_START_LINE;
+
+ if (search->original.str->str[search->original.str->len - 1] == '$')
+ search_line_type |= AT_END_LINE;
+ return search_line_type;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculating the start position of next line.
+ *
+ * @param buf editor buffer object
+ * @param current_pos current position
+ * @param max_pos max position
+ * @param end_string_symbol end of line symbol
+ * @return start position of next line
+ */
+
+static off_t
+edit_calculate_start_of_next_line (const edit_buffer_t * buf, off_t current_pos, off_t max_pos,
+ char end_string_symbol)
+{
+ off_t i;
+
+ for (i = current_pos; i < max_pos; i++)
+ {
+ current_pos++;
+ if (edit_buffer_get_byte (buf, i) == end_string_symbol)
+ break;
+ }
+
+ return current_pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculating the end position of previous line.
+ *
+ * @param buf editor buffer object
+ * @param current_pos current position
+ * @param end_string_symbol end of line symbol
+ * @return end position of previous line
+ */
+
+static off_t
+edit_calculate_end_of_previous_line (const edit_buffer_t * buf, off_t current_pos,
+ char end_string_symbol)
+{
+ off_t i;
+
+ for (i = current_pos - 1; i >= 0; i--)
+ if (edit_buffer_get_byte (buf, i) == end_string_symbol)
+ break;
+
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculating the start position of previous line.
+ *
+ * @param buf editor buffer object
+ * @param current_pos current position
+ * @param end_string_symbol end of line symbol
+ * @return start position of previous line
+ */
+
+static inline off_t
+edit_calculate_start_of_previous_line (const edit_buffer_t * buf, off_t current_pos,
+ char end_string_symbol)
+{
+ current_pos = edit_calculate_end_of_previous_line (buf, current_pos, end_string_symbol);
+ current_pos = edit_calculate_end_of_previous_line (buf, current_pos, end_string_symbol);
+
+ return (current_pos + 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculating the start position of current line.
+ *
+ * @param buf editor buffer object
+ * @param current_pos current position
+ * @param end_string_symbol end of line symbol
+ * @return start position of current line
+ */
+
+static inline off_t
+edit_calculate_start_of_current_line (const edit_buffer_t * buf, off_t current_pos,
+ char end_string_symbol)
+{
+ current_pos = edit_calculate_end_of_previous_line (buf, current_pos, end_string_symbol);
+
+ return (current_pos + 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Fixing (if needed) search start position if 'only in selection' option present.
+ *
+ * @param edit editor object
+ */
+
+static void
+edit_search_fix_search_start_if_selection (WEdit * edit)
+{
+ off_t start_mark = 0;
+ off_t end_mark = 0;
+
+ if (!edit_search_options.only_in_selection)
+ return;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return;
+
+ if (edit_search_options.backwards)
+ {
+ if (edit->search_start > end_mark || edit->search_start <= start_mark)
+ edit->search_start = end_mark;
+ }
+ else
+ {
+ if (edit->search_start < start_mark || edit->search_start >= end_mark)
+ edit->search_start = start_mark;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+edit_find (edit_search_status_msg_t * esm, gsize * len)
+{
+ WEdit *edit = esm->edit;
+ off_t search_start = edit->search_start;
+ off_t search_end;
+ off_t start_mark = 0;
+ off_t end_mark = edit->buffer.size;
+ char end_string_symbol;
+
+ end_string_symbol = edit_search_get_current_end_line_char (edit);
+
+ /* prepare for search */
+ if (edit_search_options.only_in_selection)
+ {
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ {
+ mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _(STR_E_NOTFOUND));
+ return FALSE;
+ }
+
+ /* fix the start and the end of search block positions */
+ if ((edit->search_line_type & AT_START_LINE) != 0
+ && (start_mark != 0
+ || edit_buffer_get_byte (&edit->buffer, start_mark - 1) != end_string_symbol))
+ start_mark =
+ edit_calculate_start_of_next_line (&edit->buffer, start_mark, edit->buffer.size,
+ end_string_symbol);
+
+ if ((edit->search_line_type & AT_END_LINE) != 0
+ && (end_mark - 1 != edit->buffer.size
+ || edit_buffer_get_byte (&edit->buffer, end_mark) != end_string_symbol))
+ end_mark =
+ edit_calculate_end_of_previous_line (&edit->buffer, end_mark, end_string_symbol);
+
+ if (start_mark >= end_mark)
+ {
+ mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _(STR_E_NOTFOUND));
+ return FALSE;
+ }
+ }
+ else if (edit_search_options.backwards)
+ end_mark = MAX (1, edit->buffer.curs1) - 1;
+
+ /* search */
+ if (edit_search_options.backwards)
+ {
+ /* backward search */
+ search_end = end_mark;
+
+ if ((edit->search_line_type & AT_START_LINE) != 0)
+ search_start =
+ edit_calculate_start_of_current_line (&edit->buffer, search_start,
+ end_string_symbol);
+
+ while (search_start >= start_mark)
+ {
+ gboolean ok;
+
+ if (search_end > (off_t) (search_start + edit->search->original.str->len)
+ && mc_search_is_fixed_search_str (edit->search))
+ search_end = search_start + edit->search->original.str->len;
+
+ ok = mc_search_run (edit->search, (void *) esm, search_start, search_end, len);
+
+ if (ok && edit->search->normal_offset == search_start)
+ 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 && edit->search->error != MC_SEARCH_E_NOTFOUND)
+ return FALSE;
+
+ if ((edit->search_line_type & AT_START_LINE) != 0)
+ search_start =
+ edit_calculate_start_of_previous_line (&edit->buffer, search_start,
+ end_string_symbol);
+ else
+ search_start--;
+ }
+
+ mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _(STR_E_NOTFOUND));
+ return FALSE;
+ }
+
+ /* forward search */
+ if ((edit->search_line_type & AT_START_LINE) != 0 && search_start != start_mark)
+ search_start =
+ edit_calculate_start_of_next_line (&edit->buffer, search_start, end_mark,
+ end_string_symbol);
+
+ return mc_search_run (edit->search, (void *) esm, search_start, end_mark, len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_replace_cmd__conv_to_display (const char *str)
+{
+#ifdef HAVE_CHARSET
+ GString *tmp;
+
+ tmp = str_convert_to_display (str);
+ if (tmp != NULL)
+ {
+ if (tmp->len != 0)
+ return g_string_free (tmp, FALSE);
+ g_string_free (tmp, TRUE);
+ }
+#endif
+ return g_strdup (str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_replace_cmd__conv_to_input (char *str)
+{
+#ifdef HAVE_CHARSET
+ GString *tmp;
+
+ tmp = str_convert_to_input (str);
+ if (tmp != NULL)
+ {
+ if (tmp->len != 0)
+ return g_string_free (tmp, FALSE);
+ g_string_free (tmp, TRUE);
+ }
+#endif
+ return g_strdup (str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_show_search_error (const WEdit * edit, const char *title)
+{
+ if (edit->search->error == MC_SEARCH_E_NOTFOUND)
+ edit_query_dialog (title, _(STR_E_NOTFOUND));
+ else if (edit->search->error_str != NULL)
+ edit_query_dialog (title, edit->search->error_str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_do_search (WEdit * edit)
+{
+ edit_search_status_msg_t esm;
+ gsize len = 0;
+
+ /* This shouldn't happen */
+ assert (edit->search != NULL);
+
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ esm.first = TRUE;
+ esm.edit = edit;
+ esm.offset = edit->search_start;
+
+ status_msg_init (STATUS_MSG (&esm), _("Search"), 1.0, simple_status_msg_init_cb,
+ edit_search_status_update_cb, NULL);
+
+ if (search_create_bookmark)
+ {
+ gboolean found = FALSE;
+ long l = 0, l_last = -1;
+ long q = 0;
+
+ search_create_bookmark = FALSE;
+ book_mark_flush (edit, -1);
+
+ while (mc_search_run (edit->search, (void *) &esm, q, edit->buffer.size, &len))
+ {
+ if (!found)
+ edit->search_start = edit->search->normal_offset;
+ found = TRUE;
+
+ l += edit_buffer_count_lines (&edit->buffer, q, edit->search->normal_offset);
+ if (l != l_last)
+ book_mark_insert (edit, l, BOOK_MARK_FOUND_COLOR);
+ l_last = l;
+ q = edit->search->normal_offset + 1;
+ }
+
+ if (!found)
+ edit_error_dialog (_("Search"), _(STR_E_NOTFOUND));
+ else
+ edit_cursor_move (edit, edit->search_start - edit->buffer.curs1);
+ }
+ else
+ {
+ if (edit->found_len != 0 && edit->search_start == edit->found_start + 1
+ && edit_search_options.backwards)
+ edit->search_start--;
+
+ if (edit->found_len != 0 && edit->search_start == edit->found_start - 1
+ && !edit_search_options.backwards)
+ edit->search_start++;
+
+ if (edit_find (&esm, &len))
+ {
+ edit->found_start = edit->search_start = edit->search->normal_offset;
+ edit->found_len = len;
+ edit->over_col = 0;
+ edit_cursor_move (edit, edit->search_start - edit->buffer.curs1);
+ edit_scroll_screen_over_cursor (edit);
+ if (edit_search_options.backwards)
+ edit->search_start--;
+ else
+ edit->search_start++;
+ }
+ else
+ {
+ edit->search_start = edit->buffer.curs1;
+ edit_show_search_error (edit, _("Search"));
+ }
+ }
+
+ status_msg_deinit (STATUS_MSG (&esm));
+
+ edit->force |= REDRAW_COMPLETELY;
+ edit_scroll_screen_over_cursor (edit);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_search (WEdit * edit)
+{
+ if (edit_dialog_search_show (edit))
+ {
+ edit->search_line_type = edit_get_search_line_type (edit->search);
+ edit_search_fix_search_start_if_selection (edit);
+ edit_do_search (edit);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_search_init (WEdit * edit, const char *str)
+{
+#ifdef HAVE_CHARSET
+ edit->search = mc_search_new (str, cp_source);
+#else
+ edit->search = mc_search_new (str, NULL);
+#endif
+
+ if (edit->search == NULL)
+ return FALSE;
+
+ edit->search->search_type = edit_search_options.type;
+#ifdef HAVE_CHARSET
+ edit->search->is_all_charsets = edit_search_options.all_codepages;
+#endif
+ edit->search->is_case_sensitive = edit_search_options.case_sens;
+ edit->search->whole_words = edit_search_options.whole_words;
+ edit->search->search_fn = edit_search_cmd_callback;
+ edit->search->update_fn = edit_search_update_callback;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_search_deinit (WEdit * edit)
+{
+ mc_search_free (edit->search);
+ g_free (edit->last_search_string);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mc_search_cbret_t
+edit_search_cmd_callback (const void *user_data, gsize char_offset, int *current_char)
+{
+ WEdit *edit = ((const edit_search_status_msg_t *) user_data)->edit;
+
+ *current_char = edit_buffer_get_byte (&edit->buffer, (off_t) char_offset);
+
+ return MC_SEARCH_CB_OK;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mc_search_cbret_t
+edit_search_update_callback (const void *user_data, gsize char_offset)
+{
+ status_msg_t *sm = STATUS_MSG (user_data);
+
+ ((edit_search_status_msg_t *) sm)->offset = (off_t) char_offset;
+
+ return (sm->update (sm) == B_CANCEL ? MC_SEARCH_CB_ABORT : MC_SEARCH_CB_OK);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+edit_search_status_update_cb (status_msg_t * sm)
+{
+ simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
+ edit_search_status_msg_t *esm = (edit_search_status_msg_t *) sm;
+ Widget *wd = WIDGET (sm->dlg);
+
+ if (verbose)
+ label_set_textv (ssm->label, _("Searching %s: %3d%%"), esm->edit->last_search_string,
+ edit_buffer_calc_percent (&esm->edit->buffer, esm->offset));
+ else
+ label_set_textv (ssm->label, _("Searching %s"), esm->edit->last_search_string);
+
+ if (esm->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);
+ esm->first = FALSE;
+ }
+
+ return status_msg_common_update (sm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_search_cmd (WEdit * edit, gboolean again)
+{
+ if (!again)
+ edit_search (edit);
+ else if (edit->last_search_string != NULL)
+ edit_do_search (edit);
+ 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? */
+ edit->last_search_string = (char *) history->data;
+ history->data = NULL;
+ history = g_list_first (history);
+ g_list_free_full (history, g_free);
+
+ if (edit_search_init (edit, edit->last_search_string))
+ {
+ edit_do_search (edit);
+ return;
+ }
+
+ /* found, but cannot init search */
+ MC_PTR_FREE (edit->last_search_string);
+ }
+
+ /* if not... then ask for an expression */
+ edit_search (edit);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** call with edit = 0 before shutdown to close memory leaks */
+
+void
+edit_replace_cmd (WEdit * edit, gboolean again)
+{
+ /* 1 = search string, 2 = replace with */
+ static char *saved1 = NULL; /* saved default[123] */
+ static char *saved2 = NULL;
+ char *input1 = NULL; /* user input from the dialog */
+ char *input2 = NULL;
+ GString *input2_str = NULL;
+ char *disp1 = NULL;
+ char *disp2 = NULL;
+ long times_replaced = 0;
+ gboolean once_found = FALSE;
+ edit_search_status_msg_t esm;
+
+ if (edit == NULL)
+ {
+ MC_PTR_FREE (saved1);
+ MC_PTR_FREE (saved2);
+ return;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+
+ if (again && saved1 == NULL && saved2 == NULL)
+ again = FALSE;
+
+ if (again)
+ {
+ input1 = g_strdup (saved1 != NULL ? saved1 : "");
+ input2 = g_strdup (saved2 != NULL ? saved2 : "");
+ }
+ else
+ {
+ char *tmp_inp1, *tmp_inp2;
+
+ disp1 = edit_replace_cmd__conv_to_display (saved1 != NULL ? saved1 : "");
+ disp2 = edit_replace_cmd__conv_to_display (saved2 != NULL ? saved2 : "");
+
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ edit_dialog_replace_show (edit, disp1, disp2, &input1, &input2);
+
+ g_free (disp1);
+ g_free (disp2);
+
+ if (input1 == NULL || *input1 == '\0')
+ {
+ edit->force = REDRAW_COMPLETELY;
+ goto cleanup;
+ }
+
+ tmp_inp1 = input1;
+ tmp_inp2 = input2;
+ input1 = edit_replace_cmd__conv_to_input (input1);
+ input2 = edit_replace_cmd__conv_to_input (input2);
+ g_free (tmp_inp1);
+ g_free (tmp_inp2);
+
+ g_free (saved1);
+ saved1 = g_strdup (input1);
+ g_free (saved2);
+ saved2 = g_strdup (input2);
+
+ mc_search_free (edit->search);
+ edit->search = NULL;
+ }
+
+ input2_str = g_string_new (input2);
+
+ if (edit->search == NULL)
+ {
+ if (edit_search_init (edit, input1))
+ edit_search_fix_search_start_if_selection (edit);
+ else
+ {
+ edit->search_start = edit->buffer.curs1;
+ goto cleanup;
+ }
+ }
+
+ if (edit->found_len != 0 && edit->search_start == edit->found_start + 1
+ && edit_search_options.backwards)
+ edit->search_start--;
+
+ if (edit->found_len != 0 && edit->search_start == edit->found_start - 1
+ && !edit_search_options.backwards)
+ edit->search_start++;
+
+ esm.first = TRUE;
+ esm.edit = edit;
+ esm.offset = edit->search_start;
+
+ status_msg_init (STATUS_MSG (&esm), _("Search"), 1.0, simple_status_msg_init_cb,
+ edit_search_status_update_cb, NULL);
+
+ do
+ {
+ gsize len = 0;
+
+ if (!edit_find (&esm, &len))
+ {
+ if (!(edit->search->error == MC_SEARCH_E_OK ||
+ (once_found && edit->search->error == MC_SEARCH_E_NOTFOUND)))
+ edit_show_search_error (edit, _("Search"));
+ break;
+ }
+
+ once_found = TRUE;
+
+ edit->search_start = edit->search->normal_offset;
+ /* returns negative on not found or error in pattern */
+
+ if (edit->search_start >= 0 && edit->search_start < edit->buffer.size)
+ {
+ gsize i;
+ GString *repl_str;
+
+ edit->found_start = edit->search_start;
+ edit->found_len = len;
+
+ edit_cursor_move (edit, edit->search_start - edit->buffer.curs1);
+ edit_scroll_screen_over_cursor (edit);
+
+ if (edit->replace_mode == 0)
+ {
+ long l;
+ int prompt;
+
+ l = edit->curs_row - WIDGET (edit)->rect.lines / 3;
+ if (l > 0)
+ edit_scroll_downward (edit, l);
+ if (l < 0)
+ edit_scroll_upward (edit, -l);
+
+ edit_scroll_screen_over_cursor (edit);
+ edit->force |= REDRAW_PAGE;
+ edit_render_keypress (edit);
+
+ /*so that undo stops at each query */
+ edit_push_key_press (edit);
+ /* and prompt 2/3 down */
+ disp1 = edit_replace_cmd__conv_to_display (saved1);
+ disp2 = edit_replace_cmd__conv_to_display (saved2);
+ prompt = edit_dialog_replace_prompt_show (edit, disp1, disp2, -1, -1);
+ g_free (disp1);
+ g_free (disp2);
+
+ if (prompt == B_REPLACE_ALL)
+ edit->replace_mode = 1;
+ else if (prompt == B_SKIP_REPLACE)
+ {
+ if (edit_search_options.backwards)
+ edit->search_start--;
+ else
+ edit->search_start++;
+ continue; /* loop */
+ }
+ else if (prompt == B_CANCEL)
+ {
+ edit->replace_mode = -1;
+ break; /* loop */
+ }
+ }
+
+ repl_str = mc_search_prepare_replace_str (edit->search, input2_str);
+
+ if (edit->search->error != MC_SEARCH_E_OK)
+ {
+ edit_show_search_error (edit, _("Replace"));
+ if (repl_str != NULL)
+ g_string_free (repl_str, TRUE);
+ break;
+ }
+
+ /* delete then insert new */
+ for (i = 0; i < len; i++)
+ edit_delete (edit, TRUE);
+
+ for (i = 0; i < repl_str->len; i++)
+ edit_insert (edit, repl_str->str[i]);
+
+ edit->found_len = repl_str->len;
+ g_string_free (repl_str, TRUE);
+ times_replaced++;
+
+ /* so that we don't find the same string again */
+ if (edit_search_options.backwards)
+ edit->search_start--;
+ else
+ {
+ edit->search_start += edit->found_len + (len == 0 ? 1 : 0);
+
+ if (edit->search_start >= edit->buffer.size)
+ break;
+ }
+
+ edit_scroll_screen_over_cursor (edit);
+ }
+ else
+ {
+ /* try and find from right here for next search */
+ edit->search_start = edit->buffer.curs1;
+ edit_update_curs_col (edit);
+
+ edit->force |= REDRAW_PAGE;
+ edit_render_keypress (edit);
+
+ if (times_replaced == 0)
+ query_dialog (_("Replace"), _(STR_E_NOTFOUND), D_NORMAL, 1, _("&OK"));
+ break;
+ }
+ }
+ while (edit->replace_mode >= 0);
+
+ status_msg_deinit (STATUS_MSG (&esm));
+ edit_scroll_screen_over_cursor (edit);
+ edit->force |= REDRAW_COMPLETELY;
+ edit_render_keypress (edit);
+
+ if (edit->replace_mode == 1 && times_replaced != 0)
+ message (D_NORMAL, _("Replace"), _("%ld replacements made"), times_replaced);
+
+ cleanup:
+ g_free (input1);
+ g_free (input2);
+ if (input2_str != NULL)
+ g_string_free (input2_str, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editsearch.h b/src/editor/editsearch.h
new file mode 100644
index 0000000..5fc3932
--- /dev/null
+++ b/src/editor/editsearch.h
@@ -0,0 +1,36 @@
+#ifndef MC__EDIT_SEARCH_H
+#define MC__EDIT_SEARCH_H 1
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ simple_status_msg_t status_msg; /* base class */
+
+ gboolean first;
+ WEdit *edit;
+ off_t offset;
+} edit_search_status_msg_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean edit_search_init (WEdit * edit, const char *s);
+void edit_search_deinit (WEdit * edit);
+
+mc_search_cbret_t edit_search_cmd_callback (const void *user_data, gsize char_offset,
+ int *current_char);
+mc_search_cbret_t edit_search_update_callback (const void *user_data, gsize char_offset);
+int edit_search_status_update_cb (status_msg_t * sm);
+
+void edit_search_cmd (WEdit * edit, gboolean again);
+void edit_replace_cmd (WEdit * edit, gboolean again);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EDIT_SEARCH_H */
diff --git a/src/editor/editwidget.c b/src/editor/editwidget.c
new file mode 100644
index 0000000..05f03e8
--- /dev/null
+++ b/src/editor/editwidget.c
@@ -0,0 +1,1550 @@
+/*
+ Editor initialisation and callback handler.
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru> 2012-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: editor initialisation and callback handler
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/tty/color.h" /* tty_setcolor() */
+#include "lib/skin.h"
+#include "lib/fileloc.h" /* EDIT_HOME_DIR */
+#include "lib/strutil.h" /* str_term_trim() */
+#include "lib/util.h" /* mc_build_filename() */
+#include "lib/widget.h"
+#include "lib/mcconfig.h"
+#include "lib/event.h" /* mc_event_raise() */
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/keymap.h" /* keybind_lookup_keymap_command() */
+#include "src/setup.h" /* home_dir */
+#include "src/execute.h" /* toggle_subshell() */
+#include "src/filemanager/cmd.h" /* save_setup_cmd() */
+#include "src/learn.h" /* learn_keys() */
+#include "src/args.h" /* mcedit_arg_t */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+#include "editmacros.h" /* edit_execute_macro() */
+#ifdef HAVE_ASPELL
+#include "spell.h"
+#endif
+
+/*** global variables ****************************************************************************/
+
+char *edit_window_state_char = NULL;
+char *edit_window_close_char = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define WINDOW_MIN_LINES (2 + 2)
+#define WINDOW_MIN_COLS (2 + LINE_STATE_WIDTH + 2)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static unsigned int edit_dlg_init_refcounter = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Init the 'edit' subsystem
+ */
+
+static void
+edit_dlg_init (void)
+{
+ edit_dlg_init_refcounter++;
+
+ if (edit_dlg_init_refcounter == 1)
+ {
+ edit_window_state_char = mc_skin_get ("widget-editor", "window-state-char", "*");
+ edit_window_close_char = mc_skin_get ("widget-editor", "window-close-char", "X");
+
+#ifdef HAVE_ASPELL
+ aspell_init ();
+#endif
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deinit the 'edit' subsystem
+ */
+
+static void
+edit_dlg_deinit (void)
+{
+ if (edit_dlg_init_refcounter == 1)
+ {
+ g_free (edit_window_state_char);
+ g_free (edit_window_close_char);
+
+#ifdef HAVE_ASPELL
+ aspell_clean ();
+#endif
+ }
+
+ if (edit_dlg_init_refcounter != 0)
+ edit_dlg_init_refcounter--;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show info about editor
+ */
+
+static void
+edit_about (void)
+{
+ char *ver;
+
+ ver = g_strdup_printf ("MCEdit %s", mc_global.mc_version);
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABEL (ver, NULL),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_LABEL (N_("A user friendly text editor\n"
+ "written for the Midnight Commander."), NULL),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_LABEL (N_("Copyright (C) 1996-2023 the Free Software Foundation"), NULL),
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 40 };
+
+ quick_dialog_t qdlg = {
+ r, N_("About"), "[Internal File Editor]",
+ quick_widgets, NULL, NULL
+ };
+
+ quick_widgets[0].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
+ quick_widgets[2].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
+ quick_widgets[4].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
+
+ (void) quick_dialog (&qdlg);
+ }
+
+ g_free (ver);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show a help window
+ */
+
+static void
+edit_help (void)
+{
+ ev_help_t event_data = { NULL, "[Internal File Editor]" };
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Restore saved window size.
+ *
+ * @param edit editor object
+ */
+
+static void
+edit_restore_size (WEdit * edit)
+{
+ Widget *w = WIDGET (edit);
+
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ w->mouse.forced_capture = FALSE;
+ widget_set_size_rect (w, &edit->loc_prev);
+ widget_draw (WIDGET (w->owner));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move window by one row or column in any direction.
+ *
+ * @param edit editor object
+ * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
+ */
+
+static void
+edit_window_move (WEdit * edit, long command)
+{
+ Widget *we = WIDGET (edit);
+ Widget *wo = WIDGET (we->owner);
+ WRect *w = &we->rect;
+ const WRect *wh = &wo->rect;
+
+ switch (command)
+ {
+ case CK_Up:
+ if (w->y > wh->y + 1) /* menubar */
+ w->y--;
+ break;
+ case CK_Down:
+ if (w->y < wh->y + wh->lines - 2) /* buttonbar */
+ w->y++;
+ break;
+ case CK_Left:
+ if (w->x + wh->cols > wh->x)
+ w->x--;
+ break;
+ case CK_Right:
+ if (w->x < wh->x + wh->cols)
+ w->x++;
+ break;
+ default:
+ return;
+ }
+
+ edit->force |= REDRAW_PAGE;
+ widget_draw (wo);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Resize window by one row or column in any direction.
+ *
+ * @param edit editor object
+ * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
+ */
+
+static void
+edit_window_resize (WEdit * edit, long command)
+{
+ Widget *we = WIDGET (edit);
+ Widget *wo = WIDGET (we->owner);
+ WRect *w = &we->rect;
+ const WRect *wh = &wo->rect;
+
+ switch (command)
+ {
+ case CK_Up:
+ if (w->lines > WINDOW_MIN_LINES)
+ w->lines--;
+ break;
+ case CK_Down:
+ if (w->y + w->lines < wh->y + wh->lines - 1) /* buttonbar */
+ w->lines++;
+ break;
+ case CK_Left:
+ if (w->cols > WINDOW_MIN_COLS)
+ w->cols--;
+ break;
+ case CK_Right:
+ if (w->x + w->cols < wh->x + wh->cols)
+ w->cols++;
+ break;
+ default:
+ return;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+ widget_draw (wo);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get hotkey by number.
+ *
+ * @param n number
+ * @return hotkey
+ */
+
+static unsigned char
+get_hotkey (int n)
+{
+ return (n <= 9) ? '0' + n : 'a' + n - 10;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_window_list (const WDialog * h)
+{
+ const WGroup *g = CONST_GROUP (h);
+ const size_t offset = 2; /* skip menu and buttonbar */
+ const size_t dlg_num = g_list_length (g->widgets) - offset;
+ int lines, cols;
+ Listbox *listbox;
+ GList *w;
+ WEdit *selected;
+ int i = 0;
+
+ lines = MIN ((size_t) (LINES * 2 / 3), dlg_num);
+ cols = COLS * 2 / 3;
+
+ listbox = listbox_window_new (lines, cols, _("Open files"), "[Open files]");
+
+ for (w = g->widgets; w != NULL; w = g_list_next (w))
+ if (edit_widget_is_editor (CONST_WIDGET (w->data)))
+ {
+ WEdit *e = EDIT (w->data);
+ char *fname;
+
+ if (e->filename_vpath == NULL)
+ fname = g_strdup_printf ("%c [%s]", e->modified ? '*' : ' ', _("NoName"));
+ else
+ fname =
+ g_strdup_printf ("%c%s", e->modified ? '*' : ' ',
+ vfs_path_as_str (e->filename_vpath));
+
+ listbox_add_item (listbox->list, LISTBOX_APPEND_AT_END, get_hotkey (i++),
+ str_term_trim (fname, WIDGET (listbox->list)->rect.cols - 2), e,
+ FALSE);
+ g_free (fname);
+ }
+
+ selected = listbox_run_with_data (listbox, g->current->data);
+ if (selected != NULL)
+ widget_select (WIDGET (selected));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_get_shortcut (long command)
+{
+ const char *ext_map;
+ const char *shortcut = NULL;
+
+ shortcut = keybind_lookup_keymap_shortcut (editor_map, command);
+ if (shortcut != NULL)
+ return g_strdup (shortcut);
+
+ ext_map = keybind_lookup_keymap_shortcut (editor_map, CK_ExtendedKeyMap);
+ if (ext_map != NULL)
+ shortcut = keybind_lookup_keymap_shortcut (editor_x_map, command);
+ if (shortcut != NULL)
+ return g_strdup_printf ("%s %s", ext_map, shortcut);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_get_title (const WDialog * h, size_t len)
+{
+ const WEdit *edit;
+ const char *modified;
+ const char *file_label;
+ char *filename;
+
+ edit = edit_find_editor (h);
+ modified = edit->modified ? "(*) " : " ";
+
+ len -= 4;
+
+ if (edit->filename_vpath == NULL)
+ filename = g_strdup (_("[NoName]"));
+ else
+ filename = g_strdup (vfs_path_as_str (edit->filename_vpath));
+
+ file_label = str_term_trim (filename, len - str_term_width1 (_("Edit: ")));
+ g_free (filename);
+
+ return g_strconcat (_("Edit: "), modified, file_label, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_dialog_command_execute (WDialog * h, long command)
+{
+ WGroup *g = GROUP (h);
+ cb_ret_t ret = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_EditNew:
+ edit_load_file_from_filename (h, NULL, 0);
+ break;
+ case CK_EditFile:
+ edit_load_cmd (h);
+ break;
+ case CK_History:
+ edit_load_file_from_history (h);
+ break;
+ case CK_EditSyntaxFile:
+ edit_load_syntax_file (h);
+ break;
+ case CK_EditUserMenu:
+ edit_load_menu_file (h);
+ break;
+ case CK_Close:
+ /* if there are no opened files anymore, close MC editor */
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)) &&
+ edit_close_cmd (EDIT (g->current->data)) && edit_find_editor (h) == NULL)
+ dlg_close (h);
+ break;
+ case CK_Help:
+ edit_help ();
+ /* edit->force |= REDRAW_COMPLETELY; */
+ break;
+ case CK_Menu:
+ edit_menu_cmd (h);
+ break;
+ case CK_Quit:
+ case CK_Cancel:
+ /* don't close editor due to SIGINT, but stop move/resize window */
+ {
+ Widget *w = WIDGET (g->current->data);
+
+ if (edit_widget_is_editor (w) && EDIT (w)->drag_state != MCEDIT_DRAG_NONE)
+ edit_restore_size (EDIT (w));
+ else if (command == CK_Quit)
+ dlg_close (h);
+ }
+ break;
+ case CK_About:
+ edit_about ();
+ break;
+ case CK_SyntaxOnOff:
+ edit_syntax_onoff_cmd (h);
+ break;
+ case CK_ShowTabTws:
+ edit_show_tabs_tws_cmd (h);
+ break;
+ case CK_ShowMargin:
+ edit_show_margin_cmd (h);
+ break;
+ case CK_ShowNumbers:
+ edit_show_numbers_cmd (h);
+ break;
+ case CK_Refresh:
+ edit_refresh_cmd ();
+ break;
+ case CK_Shell:
+ toggle_subshell ();
+ break;
+ case CK_LearnKeys:
+ learn_keys ();
+ break;
+ case CK_WindowMove:
+ case CK_WindowResize:
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
+ edit_handle_move_resize (EDIT (g->current->data), command);
+ break;
+ case CK_WindowList:
+ edit_window_list (h);
+ break;
+ case CK_WindowNext:
+ group_select_next_widget (g);
+ break;
+ case CK_WindowPrev:
+ group_select_prev_widget (g);
+ break;
+ case CK_Options:
+ edit_options_dialog (h);
+ break;
+ case CK_OptionsSaveMode:
+ edit_save_mode_cmd ();
+ break;
+ case CK_SaveSetup:
+ save_setup_cmd ();
+ break;
+ default:
+ ret = MSG_NOT_HANDLED;
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Translate the keycode into either 'command' or 'char_for_insertion'.
+ * 'command' is one of the editor commands from lib/keybind.h.
+ */
+
+static gboolean
+edit_translate_key (WEdit * edit, long x_key, int *cmd, int *ch)
+{
+ Widget *w = WIDGET (edit);
+ long command = CK_InsertChar;
+ int char_for_insertion = -1;
+
+ /* an ordinary insertable character */
+ if (!w->ext_mode && x_key < 256)
+ {
+#ifndef HAVE_CHARSET
+ if (is_printable (x_key))
+ {
+ char_for_insertion = x_key;
+ goto fin;
+ }
+#else
+ int c;
+
+ if (edit->charpoint >= MB_LEN_MAX)
+ {
+ edit->charpoint = 0;
+ edit->charbuf[edit->charpoint] = '\0';
+ }
+ if (edit->charpoint < MB_LEN_MAX)
+ {
+ edit->charbuf[edit->charpoint++] = x_key;
+ edit->charbuf[edit->charpoint] = '\0';
+ }
+
+ /* input from 8-bit locale */
+ if (!mc_global.utf8_display)
+ {
+ /* source is in 8-bit codeset */
+ c = convert_from_input_c (x_key);
+
+ if (is_printable (c))
+ {
+ if (!edit->utf8)
+ char_for_insertion = c;
+ else
+ char_for_insertion = convert_from_8bit_to_utf_c2 ((char) x_key);
+ goto fin;
+ }
+ }
+ else
+ {
+ /* UTF-8 locale */
+ int res;
+
+ res = str_is_valid_char (edit->charbuf, edit->charpoint);
+ if (res < 0 && res != -2)
+ {
+ edit->charpoint = 0; /* broken multibyte char, skip */
+ goto fin;
+ }
+
+ if (edit->utf8)
+ {
+ /* source is in UTF-8 codeset */
+ if (res < 0)
+ {
+ char_for_insertion = x_key;
+ goto fin;
+ }
+
+ edit->charbuf[edit->charpoint] = '\0';
+ edit->charpoint = 0;
+ if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
+ {
+ char_for_insertion = x_key;
+ goto fin;
+ }
+ }
+ else
+ {
+ /* 8-bit source */
+ if (res < 0)
+ {
+ /* not finished multibyte input (we're in the middle of multibyte utf-8 char) */
+ goto fin;
+ }
+
+ if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
+ {
+ c = convert_from_utf_to_current (edit->charbuf);
+ edit->charbuf[0] = '\0';
+ edit->charpoint = 0;
+ char_for_insertion = c;
+ goto fin;
+ }
+
+ /* non-printable utf-8 input, skip it */
+ edit->charbuf[0] = '\0';
+ edit->charpoint = 0;
+ }
+ }
+#endif /* HAVE_CHARSET */
+ }
+
+ /* Commands specific to the key emulation */
+ command = widget_lookup_key (w, x_key);
+ if (command == CK_IgnoreKey)
+ command = CK_InsertChar;
+
+ fin:
+ *cmd = (int) command; /* FIXME */
+ *ch = char_for_insertion;
+
+ return !(command == CK_InsertChar && char_for_insertion == -1);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_quit (WDialog * h)
+{
+ GList *l;
+ WEdit *e = NULL;
+ GSList *m = NULL;
+ GSList *me;
+
+ /* don't stop the dialog before final decision */
+ widget_set_state (WIDGET (h), WST_ACTIVE, TRUE);
+
+ /* check window state and get modified files */
+ for (l = GROUP (h)->widgets; l != NULL; l = g_list_next (l))
+ if (edit_widget_is_editor (CONST_WIDGET (l->data)))
+ {
+ e = EDIT (l->data);
+
+ if (e->drag_state != MCEDIT_DRAG_NONE)
+ {
+ edit_restore_size (e);
+ g_slist_free (m);
+ return;
+ }
+
+ /* create separate list because widget_select()
+ changes the window position in Z order */
+ if (e->modified)
+ m = g_slist_prepend (m, l->data);
+ }
+
+ for (me = m; me != NULL; me = g_slist_next (me))
+ {
+ e = EDIT (me->data);
+
+ widget_select (WIDGET (e));
+
+ if (!edit_ok_to_exit (e))
+ break;
+ }
+
+ /* if all files were checked, quit editor */
+ if (me == NULL)
+ dlg_close (h);
+
+ g_slist_free (m);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_set_buttonbar (WEdit * edit, WButtonBar * bb)
+{
+ Widget *w = WIDGET (edit);
+
+ buttonbar_set_label (bb, 1, Q_ ("ButtonBar|Help"), w->keymap, NULL);
+ buttonbar_set_label (bb, 2, Q_ ("ButtonBar|Save"), w->keymap, w);
+ buttonbar_set_label (bb, 3, Q_ ("ButtonBar|Mark"), w->keymap, w);
+ buttonbar_set_label (bb, 4, Q_ ("ButtonBar|Replac"), w->keymap, w);
+ buttonbar_set_label (bb, 5, Q_ ("ButtonBar|Copy"), w->keymap, w);
+ buttonbar_set_label (bb, 6, Q_ ("ButtonBar|Move"), w->keymap, w);
+ buttonbar_set_label (bb, 7, Q_ ("ButtonBar|Search"), w->keymap, w);
+ buttonbar_set_label (bb, 8, Q_ ("ButtonBar|Delete"), w->keymap, w);
+ buttonbar_set_label (bb, 9, Q_ ("ButtonBar|PullDn"), w->keymap, NULL);
+ buttonbar_set_label (bb, 10, Q_ ("ButtonBar|Quit"), w->keymap, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_total_update (WEdit * edit)
+{
+ edit_find_bracket (edit);
+ edit->force |= REDRAW_COMPLETELY;
+ edit_update_curs_row (edit);
+ edit_update_screen (edit);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+edit_update_cursor (WEdit * edit, const mouse_event_t * event)
+{
+ int x, y;
+ gboolean done;
+
+ x = event->x - (edit->fullscreen ? 0 : 1);
+ y = event->y - (edit->fullscreen ? 0 : 1);
+
+ if (edit->mark2 != -1 && event->msg == MSG_MOUSE_UP)
+ return TRUE; /* don't do anything */
+
+ if (event->msg == MSG_MOUSE_DOWN || event->msg == MSG_MOUSE_UP)
+ edit_push_key_press (edit);
+
+ if (!edit_options.cursor_beyond_eol)
+ edit->prev_col = x - edit->start_col - edit_options.line_state_width;
+ else
+ {
+ long line_len;
+
+ line_len =
+ edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
+ edit_buffer_get_current_eol (&edit->buffer));
+
+ if (x > line_len - 1)
+ {
+ edit->over_col = x - line_len - edit->start_col - edit_options.line_state_width;
+ edit->prev_col = line_len;
+ }
+ else
+ {
+ edit->over_col = 0;
+ edit->prev_col = x - edit_options.line_state_width - edit->start_col;
+ }
+ }
+
+ if (y > edit->curs_row)
+ edit_move_down (edit, y - edit->curs_row, FALSE);
+ else if (y < edit->curs_row)
+ edit_move_up (edit, edit->curs_row - y, FALSE);
+ else
+ edit_move_to_prev_col (edit, edit_buffer_get_current_bol (&edit->buffer));
+
+ if (event->msg == MSG_MOUSE_CLICK)
+ {
+ edit_mark_cmd (edit, TRUE); /* reset */
+ edit->highlight = 0;
+ }
+
+ done = (event->msg != MSG_MOUSE_DRAG);
+ if (done)
+ edit_mark_cmd (edit, FALSE);
+
+ return done;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Callback for the edit dialog */
+
+static cb_ret_t
+edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroup *g = GROUP (w);
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ edit_dlg_init ();
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ dlg_default_callback (w, NULL, MSG_RESIZE, 0, NULL);
+ menubar_arrange (menubar_find (h));
+ return MSG_HANDLED;
+
+ case MSG_ACTION:
+ {
+ /* Handle shortcuts, menu, and buttonbar. */
+
+ cb_ret_t result;
+
+ result = edit_dialog_command_execute (h, parm);
+
+ /* We forward any commands coming from the menu, and which haven't been
+ handled by the dialog, to the focused WEdit window. */
+ if (result == MSG_NOT_HANDLED && sender == WIDGET (menubar_find (h)))
+ result = send_message (g->current->data, NULL, MSG_ACTION, parm, NULL);
+
+ return result;
+ }
+
+ case MSG_KEY:
+ {
+ Widget *we = WIDGET (g->current->data);
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+ if (edit_widget_is_editor (we))
+ {
+ gboolean ext_mode;
+ long command;
+
+ /* keep and then extmod flag */
+ ext_mode = we->ext_mode;
+ command = widget_lookup_key (we, parm);
+ we->ext_mode = ext_mode;
+
+ if (command == CK_IgnoreKey)
+ we->ext_mode = FALSE;
+ else
+ {
+ ret = edit_dialog_command_execute (h, command);
+ /* if command was not handled, keep the extended mode
+ for the further key processing */
+ if (ret == MSG_HANDLED)
+ we->ext_mode = FALSE;
+ }
+ }
+
+ /*
+ * Due to the "end of bracket" escape the editor sees input with is_idle() == false
+ * (expects more characters) and hence doesn't yet refresh the screen, but then
+ * no further characters arrive (there's only an "end of bracket" which is swallowed
+ * by tty_get_event()), so you end up with a screen that's not refreshed after pasting.
+ * So let's trigger an IDLE signal.
+ */
+ if (!is_idle ())
+ widget_idle (w, TRUE);
+ return ret;
+ }
+
+ /* hardcoded menu hotkeys (see edit_drop_hotkey_menu) */
+ case MSG_UNHANDLED_KEY:
+ return edit_drop_hotkey_menu (h, parm) ? MSG_HANDLED : MSG_NOT_HANDLED;
+
+ case MSG_VALIDATE:
+ edit_quit (h);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ edit_dlg_deinit ();
+ return MSG_HANDLED;
+
+ case MSG_IDLE:
+ widget_idle (w, FALSE);
+ return send_message (g->current->data, NULL, MSG_IDLE, 0, NULL);
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handle mouse events of editor screen.
+ *
+ * @param w Widget object (the editor)
+ * @param msg mouse event message
+ * @param event mouse event data
+ */
+static void
+edit_dialog_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ gboolean unhandled = TRUE;
+
+ if (msg == MSG_MOUSE_DOWN && event->y == 0)
+ {
+ WGroup *g = GROUP (w);
+ WDialog *h = DIALOG (w);
+ WMenuBar *b;
+
+ b = menubar_find (h);
+
+ if (!widget_get_state (WIDGET (b), WST_FOCUSED))
+ {
+ /* menubar */
+
+ GList *l;
+ GList *top = NULL;
+ int x;
+
+ /* Try find top fullscreen window */
+ for (l = g->widgets; l != NULL; l = g_list_next (l))
+ if (edit_widget_is_editor (CONST_WIDGET (l->data)) && EDIT (l->data)->fullscreen)
+ top = l;
+
+ /* Handle fullscreen/close buttons in the top line */
+ x = w->rect.cols - 6;
+
+ if (top != NULL && event->x >= x)
+ {
+ WEdit *e = EDIT (top->data);
+
+ if (top != g->current)
+ {
+ /* Window is not active. Activate it */
+ widget_select (WIDGET (e));
+ }
+
+ /* Handle buttons */
+ if (event->x - x <= 2)
+ edit_toggle_fullscreen (e);
+ else
+ send_message (h, NULL, MSG_ACTION, CK_Close, NULL);
+
+ unhandled = FALSE;
+ }
+
+ if (unhandled)
+ menubar_activate (b, drop_menus, -1);
+ }
+ }
+
+ /* Continue handling of unhandled event in window or menu */
+ event->result.abort = unhandled;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_dialog_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_INIT:
+ w->rect = WIDGET (w->owner)->rect;
+ rect_grow (&w->rect, -1, 0);
+ w->pos_flags |= WPOS_KEEP_ALL;
+ return MSG_HANDLED;
+
+ default:
+ return background_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WEdit *e = EDIT (w);
+
+ switch (msg)
+ {
+ case MSG_FOCUS:
+ edit_set_buttonbar (e, buttonbar_find (DIALOG (w->owner)));
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ e->force |= REDRAW_COMPLETELY;
+ edit_update_screen (e);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ {
+ int cmd, ch;
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+ /* The user may override the access-keys for the menu bar. */
+ if (macro_index == -1 && edit_execute_macro (e, parm))
+ {
+ edit_update_screen (e);
+ ret = MSG_HANDLED;
+ }
+ else if (edit_translate_key (e, parm, &cmd, &ch))
+ {
+ edit_execute_key_command (e, cmd, ch);
+ edit_update_screen (e);
+ ret = MSG_HANDLED;
+ }
+
+ return ret;
+ }
+
+ case MSG_ACTION:
+ /* command from menubar or buttonbar */
+ edit_execute_key_command (e, parm, -1);
+ edit_update_screen (e);
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ {
+ int y, x;
+
+ y = (e->fullscreen ? 0 : 1) + EDIT_TEXT_VERTICAL_OFFSET + e->curs_row;
+ x = (e->fullscreen ? 0 : 1) + EDIT_TEXT_HORIZONTAL_OFFSET +
+ edit_options.line_state_width + e->curs_col + e->start_col + e->over_col;
+
+ widget_gotoyx (w, y, x);
+ return MSG_HANDLED;
+ }
+
+ case MSG_IDLE:
+ edit_update_screen (e);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ edit_clean (e);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handle move/resize mouse events.
+ */
+static void
+edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WEdit *edit = EDIT (w);
+ WRect *r = &w->rect;
+ const WRect *h = &CONST_WIDGET (w->owner)->rect;
+ int global_x, global_y;
+
+ if (msg == MSG_MOUSE_UP)
+ {
+ /* Exit move/resize mode. */
+ edit_execute_cmd (edit, CK_Enter, -1);
+ edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
+ return;
+ }
+
+ if (msg != MSG_MOUSE_DRAG)
+ /**
+ * We ignore any other events. Specifically, MSG_MOUSE_DOWN.
+ *
+ * When the move/resize is initiated by the menu, we let the user
+ * stop it by clicking with the mouse. Which is why we don't want
+ * a mouse down to affect the window.
+ */
+ return;
+
+ /* Convert point to global coordinates for easier calculations. */
+ global_x = event->x + r->x;
+ global_y = event->y + r->y;
+
+ /* Clamp the point to the dialog's client area. */
+ global_y = CLAMP (global_y, h->y + 1, h->y + h->lines - 2); /* Status line, buttonbar */
+ global_x = CLAMP (global_x, h->x, h->x + h->cols - 1); /* Currently a no-op, as the dialog has no left/right margins. */
+
+ if (edit->drag_state == MCEDIT_DRAG_MOVE)
+ {
+ r->y = global_y;
+ r->x = global_x - edit->drag_state_start;
+ }
+ else if (edit->drag_state == MCEDIT_DRAG_RESIZE)
+ {
+ r->lines = MAX (WINDOW_MIN_LINES, global_y - r->y + 1);
+ r->cols = MAX (WINDOW_MIN_COLS, global_x - r->x + 1);
+ }
+
+ edit->force |= REDRAW_COMPLETELY; /* Not really needed as WEdit's MSG_DRAW already does this. */
+
+ /* We draw the whole dialog because dragging/resizing exposes area beneath. */
+ widget_draw (WIDGET (w->owner));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handle mouse events of editor window
+ *
+ * @param w Widget object (the editor window)
+ * @param msg mouse event message
+ * @param event mouse event data
+ */
+static void
+edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WEdit *edit = EDIT (w);
+ /* buttons' distance from right edge */
+ int dx = edit->fullscreen ? 0 : 2;
+ /* location of 'Close' and 'Toggle fullscreen' pictograms */
+ int close_x, toggle_fullscreen_x;
+
+ close_x = (w->rect.cols - 1) - dx - 1;
+ toggle_fullscreen_x = close_x - 3;
+
+ if (edit->drag_state != MCEDIT_DRAG_NONE)
+ {
+ /* window is being resized/moved */
+ edit_mouse_handle_move_resize (w, msg, event);
+ return;
+ }
+
+ /* If it's the last line on the screen, we abort the event to make the
+ * system channel it to the overlapping buttonbar instead. We have to do
+ * this because a WEdit has the WOP_TOP_SELECT flag, which makes it above
+ * the buttonbar in Z-order. */
+ if (msg == MSG_MOUSE_DOWN && (event->y + w->rect.y == LINES - 1))
+ {
+ event->result.abort = TRUE;
+ return;
+ }
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+ edit_update_curs_row (edit);
+ edit_update_curs_col (edit);
+
+ if (!edit->fullscreen)
+ {
+ if (event->y == 0)
+ {
+ if (event->x >= close_x - 1 && event->x <= close_x + 1)
+ ; /* do nothing (see MSG_MOUSE_CLICK) */
+ else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
+ ; /* do nothing (see MSG_MOUSE_CLICK) */
+ else
+ {
+ /* start window move */
+ edit_execute_cmd (edit, CK_WindowMove, -1);
+ edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
+ edit->drag_state_start = event->x;
+ }
+ break;
+ }
+
+ if (event->y == w->rect.lines - 1 && event->x == w->rect.cols - 1)
+ {
+ /* bottom-right corner -- start window resize */
+ edit_execute_cmd (edit, CK_WindowResize, -1);
+ break;
+ }
+ }
+
+ MC_FALLTHROUGH; /* to start/stop text selection */
+
+ case MSG_MOUSE_UP:
+ edit_update_cursor (edit, event);
+ edit_total_update (edit);
+ break;
+
+ case MSG_MOUSE_CLICK:
+ if (event->y == 0)
+ {
+ if (event->x >= close_x - 1 && event->x <= close_x + 1)
+ send_message (w->owner, NULL, MSG_ACTION, CK_Close, NULL);
+ else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
+ edit_toggle_fullscreen (edit);
+ else if (!edit->fullscreen && event->count == GPM_DOUBLE)
+ /* double click on top line (toggle fullscreen) */
+ edit_toggle_fullscreen (edit);
+ }
+ else if (event->count == GPM_DOUBLE)
+ {
+ /* double click */
+ edit_mark_current_word_cmd (edit);
+ edit_total_update (edit);
+ }
+ else if (event->count == GPM_TRIPLE)
+ {
+ /* triple click: works in GPM only, not in xterm */
+ edit_mark_current_line_cmd (edit);
+ edit_total_update (edit);
+ }
+ break;
+
+ case MSG_MOUSE_DRAG:
+ edit_update_cursor (edit, event);
+ edit_total_update (edit);
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ edit_move_up (edit, 2, TRUE);
+ edit_total_update (edit);
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ edit_move_down (edit, 2, TRUE);
+ edit_total_update (edit);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Edit one file.
+ *
+ * @param file_vpath file object
+ * @param line line number
+ * @return TRUE if no errors was occurred, FALSE otherwise
+ */
+
+gboolean
+edit_file (const vfs_path_t * file_vpath, long line)
+{
+ mcedit_arg_t arg = { (vfs_path_t *) file_vpath, line };
+ GList *files;
+ gboolean ok;
+
+ files = g_list_prepend (NULL, &arg);
+ ok = edit_files (files);
+ g_list_free (files);
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_files (const GList * files)
+{
+ static gboolean made_directory = FALSE;
+ WDialog *edit_dlg;
+ WGroup *g;
+ WMenuBar *menubar;
+ Widget *w, *wd;
+ const GList *file;
+ gboolean ok = FALSE;
+
+ if (!made_directory)
+ {
+ char *dir;
+
+ dir = mc_build_filename (mc_config_get_cache_path (), EDIT_HOME_DIR, (char *) NULL);
+ made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
+ g_free (dir);
+
+ dir = mc_build_filename (mc_config_get_path (), EDIT_HOME_DIR, (char *) NULL);
+ made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
+ g_free (dir);
+
+ dir = mc_build_filename (mc_config_get_data_path (), EDIT_HOME_DIR, (char *) NULL);
+ made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
+ g_free (dir);
+ }
+
+ /* Create a new dialog and add it widgets to it */
+ edit_dlg =
+ dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, edit_dialog_callback,
+ edit_dialog_mouse_callback, "[Internal File Editor]", NULL);
+ wd = WIDGET (edit_dlg);
+ widget_want_tab (wd, TRUE);
+ wd->keymap = editor_map;
+ wd->ext_keymap = editor_x_map;
+
+ edit_dlg->get_shortcut = edit_get_shortcut;
+ edit_dlg->get_title = edit_get_title;
+
+ g = GROUP (edit_dlg);
+
+ edit_dlg->bg =
+ WIDGET (background_new
+ (1, 0, wd->rect.lines - 2, wd->rect.cols, EDITOR_BACKGROUND, ' ',
+ edit_dialog_bg_callback));
+ group_add_widget (g, edit_dlg->bg);
+
+ menubar = menubar_new (NULL);
+ w = WIDGET (menubar);
+ group_add_widget_autopos (g, w, w->pos_flags, NULL);
+ edit_init_menu (menubar);
+
+ w = WIDGET (buttonbar_new ());
+ group_add_widget_autopos (g, w, w->pos_flags, NULL);
+
+ for (file = files; file != NULL; file = g_list_next (file))
+ {
+ mcedit_arg_t *f = (mcedit_arg_t *) file->data;
+ gboolean f_ok;
+
+ f_ok = edit_load_file_from_filename (edit_dlg, f->file_vpath, f->line_number);
+ /* at least one file has been opened succefully */
+ ok = ok || f_ok;
+ }
+
+ if (ok)
+ dlg_run (edit_dlg);
+
+ if (!ok || widget_get_state (wd, WST_CLOSED))
+ widget_destroy (wd);
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+edit_get_file_name (const WEdit * edit)
+{
+ return vfs_path_as_str (edit->filename_vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WEdit *
+edit_find_editor (const WDialog * h)
+{
+ const WGroup *g = CONST_GROUP (h);
+
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
+ return EDIT (g->current->data);
+ return EDIT (widget_find_by_type (CONST_WIDGET (h), edit_callback));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check if widget is an WEdit class.
+ *
+ * @param w probably editor object
+ * @return TRUE if widget is an WEdit class, FALSE otherwise
+ */
+
+gboolean
+edit_widget_is_editor (const Widget * w)
+{
+ return (w != NULL && w->callback == edit_callback);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_update_screen (WEdit * e)
+{
+ edit_scroll_screen_over_cursor (e);
+ edit_update_curs_col (e);
+ edit_status (e, widget_get_state (WIDGET (e), WST_FOCUSED));
+
+ /* pop all events for this window for internal handling */
+ if (!is_idle ())
+ e->force |= REDRAW_PAGE;
+ else
+ {
+ if ((e->force & REDRAW_COMPLETELY) != 0)
+ e->force |= REDRAW_PAGE;
+ edit_render_keypress (e);
+ }
+
+ widget_draw (WIDGET (buttonbar_find (DIALOG (WIDGET (e)->owner))));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Save current window size.
+ *
+ * @param edit editor object
+ */
+
+void
+edit_save_size (WEdit * edit)
+{
+ edit->loc_prev = WIDGET (edit)->rect;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create new editor window and insert it into editor screen.
+ *
+ * @param h editor dialog (screen)
+ * @param y y coordinate
+ * @param x x coordinate
+ * @param lines window height
+ * @param cols window width
+ * @param f file object
+ * @param fline line number in file
+ * @return TRUE if new window was successfully created and inserted into editor screen,
+ * FALSE otherwise
+ */
+
+gboolean
+edit_add_window (WDialog * h, const WRect * r, const vfs_path_t * f, long fline)
+{
+ WEdit *edit;
+ Widget *w;
+
+ edit = edit_init (NULL, r, f, fline);
+ if (edit == NULL)
+ return FALSE;
+
+ w = WIDGET (edit);
+ w->callback = edit_callback;
+ w->mouse_callback = edit_mouse_callback;
+
+ group_add_widget_autopos (GROUP (h), w, WPOS_KEEP_ALL, NULL);
+ edit_set_buttonbar (edit, buttonbar_find (h));
+ widget_draw (WIDGET (h));
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Handle move/resize events.
+ *
+ * @param edit editor object
+ * @param command action id
+ * @return TRUE if the action was handled, FALSE otherwise
+ */
+
+gboolean
+edit_handle_move_resize (WEdit * edit, long command)
+{
+ Widget *w = WIDGET (edit);
+ gboolean ret = FALSE;
+
+ if (edit->fullscreen)
+ {
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ w->mouse.forced_capture = FALSE;
+ return ret;
+ }
+
+ switch (edit->drag_state)
+ {
+ case MCEDIT_DRAG_NONE:
+ /* possible start move/resize */
+ switch (command)
+ {
+ case CK_WindowMove:
+ edit->drag_state = MCEDIT_DRAG_MOVE;
+ edit_save_size (edit);
+ edit_status (edit, TRUE); /* redraw frame and status */
+ /**
+ * If a user initiates a move by the menu, not by the mouse, we
+ * make a subsequent mouse drag pull the frame from its middle.
+ * (We can instead choose '0' to pull it from the corner.)
+ */
+ edit->drag_state_start = w->rect.cols / 2;
+ ret = TRUE;
+ break;
+ case CK_WindowResize:
+ edit->drag_state = MCEDIT_DRAG_RESIZE;
+ edit_save_size (edit);
+ edit_status (edit, TRUE); /* redraw frame and status */
+ ret = TRUE;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case MCEDIT_DRAG_MOVE:
+ switch (command)
+ {
+ case CK_WindowResize:
+ edit->drag_state = MCEDIT_DRAG_RESIZE;
+ ret = TRUE;
+ break;
+ case CK_Up:
+ case CK_Down:
+ case CK_Left:
+ case CK_Right:
+ edit_window_move (edit, command);
+ ret = TRUE;
+ break;
+ case CK_Enter:
+ case CK_WindowMove:
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ edit_status (edit, TRUE); /* redraw frame and status */
+ MC_FALLTHROUGH;
+ default:
+ ret = TRUE;
+ break;
+ }
+ break;
+
+ case MCEDIT_DRAG_RESIZE:
+ switch (command)
+ {
+ case CK_WindowMove:
+ edit->drag_state = MCEDIT_DRAG_MOVE;
+ ret = TRUE;
+ break;
+ case CK_Up:
+ case CK_Down:
+ case CK_Left:
+ case CK_Right:
+ edit_window_resize (edit, command);
+ ret = TRUE;
+ break;
+ case CK_Enter:
+ case CK_WindowResize:
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ edit_status (edit, TRUE); /* redraw frame and status */
+ MC_FALLTHROUGH;
+ default:
+ ret = TRUE;
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /**
+ * - We let the user stop a resize/move operation by clicking with the
+ * mouse anywhere. ("clicking" = pressing and releasing a button.)
+ * - We let the user perform a resize/move operation by a mouse drag
+ * initiated anywhere.
+ *
+ * "Anywhere" means: inside or outside the window. We make this happen
+ * with the 'forced_capture' flag.
+ */
+ w->mouse.forced_capture = (edit->drag_state != MCEDIT_DRAG_NONE);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle window fuulscreen mode.
+ *
+ * @param edit editor object
+ */
+
+void
+edit_toggle_fullscreen (WEdit * edit)
+{
+ Widget *w = WIDGET (edit);
+
+ edit->fullscreen = !edit->fullscreen;
+ edit->force = REDRAW_COMPLETELY;
+
+ if (!edit->fullscreen)
+ {
+ edit_restore_size (edit);
+ /* do not follow screen size on resize */
+ w->pos_flags = WPOS_KEEP_DEFAULT;
+ }
+ else
+ {
+ WRect r;
+
+ edit_save_size (edit);
+ r = WIDGET (w->owner)->rect;
+ rect_grow (&r, -1, 0);
+ widget_set_size_rect (w, &r);
+ /* follow screen size on resize */
+ w->pos_flags = WPOS_KEEP_ALL;
+ edit->force |= REDRAW_PAGE;
+ edit_update_screen (edit);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editwidget.h b/src/editor/editwidget.h
new file mode 100644
index 0000000..769b91a
--- /dev/null
+++ b/src/editor/editwidget.h
@@ -0,0 +1,173 @@
+/** \file
+ * \brief Header: editor widget WEdit
+ */
+
+#ifndef MC__EDIT_WIDGET_H
+#define MC__EDIT_WIDGET_H
+
+#include <limits.h> /* MB_LEN_MAX */
+
+#include "lib/search.h" /* mc_search_t */
+#include "lib/widget.h" /* Widget */
+
+#include "edit-impl.h"
+#include "editbuffer.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define N_LINE_CACHES 32
+
+/*** enums ***************************************************************************************/
+
+/**
+ enum for store the search conditions check results.
+ (if search condition have BOL(^) or EOL ($) regexp checial characters).
+*/
+typedef enum
+{
+ AT_START_LINE = (1 << 0),
+ AT_END_LINE = (1 << 1)
+} edit_search_line_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct edit_book_mark_t edit_book_mark_t;
+struct edit_book_mark_t
+{
+ long line; /* line number */
+ int c; /* color */
+ edit_book_mark_t *next;
+ edit_book_mark_t *prev;
+};
+
+typedef struct edit_syntax_rule_t edit_syntax_rule_t;
+struct edit_syntax_rule_t
+{
+ unsigned short keyword;
+ off_t end;
+ unsigned char context;
+ unsigned char _context;
+ unsigned char border;
+};
+
+/*
+ * State of WEdit window
+ * MCEDIT_DRAG_NONE - window is in normal mode
+ * MCEDIT_DRAG_MOVE - window is being moved
+ * MCEDIT_DRAG_RESIZE - window is being resized
+ */
+typedef enum
+{
+ MCEDIT_DRAG_NONE = 0,
+ MCEDIT_DRAG_MOVE,
+ MCEDIT_DRAG_RESIZE
+} mcedit_drag_state_t;
+
+struct WEdit
+{
+ Widget widget;
+ mcedit_drag_state_t drag_state;
+ int drag_state_start; /* save cursor position before window moving */
+
+ /* save location before move/resize or toggle to fullscreen */
+ WRect loc_prev;
+
+ vfs_path_t *filename_vpath; /* Name of the file */
+ vfs_path_t *dir_vpath; /* NULL if filename is absolute */
+
+ /* dynamic buffers and cursor position for editor: */
+ edit_buffer_t buffer;
+
+#ifdef HAVE_CHARSET
+ /* multibyte support */
+ gboolean utf8; /* It's multibyte file codeset */
+ GIConv converter;
+ char charbuf[MB_LEN_MAX + 1];
+ int charpoint;
+#endif
+
+ /* search handler */
+ mc_search_t *search;
+ int replace_mode;
+ /* is search conditions should be started from BOL(^) or ended with EOL($) */
+ edit_search_line_t search_line_type;
+
+ char *last_search_string; /* String that have been searched */
+ off_t search_start; /* First character to start searching from */
+ unsigned long found_len; /* Length of found string or 0 if none was found */
+ off_t found_start; /* the found word from a search - start position */
+
+ /* display information */
+ long start_display; /* First char displayed */
+ long start_col; /* First displayed column, negative */
+ long max_column; /* The maximum cursor position ever reached used to calc hori scroll bar */
+ long curs_row; /* row position of cursor on the screen */
+ long curs_col; /* column position on screen */
+ long over_col; /* pos after '\n' */
+ int force; /* how much of the screen do we redraw? */
+ unsigned int overwrite:1; /* Overwrite on type mode (as opposed to insert) */
+ unsigned int modified:1; /* File has been modified and needs saving */
+ unsigned int loading_done:1; /* File has been loaded into the editor */
+ unsigned int locked:1; /* We hold lock on current file */
+ unsigned int delete_file:1; /* New file, needs to be deleted unless modified */
+ unsigned int highlight:1; /* There is a selected block */
+ unsigned int column_highlight:1;
+ unsigned int fullscreen:1; /* Is window fullscreen or not */
+ long prev_col; /* recent column position of the cursor - used when moving
+ up or down past lines that are shorter than the current line */
+ long start_line; /* line number of the top of the page */
+
+ /* file info */
+ off_t mark1; /* position of highlight start */
+ off_t mark2; /* position of highlight end */
+ off_t end_mark_curs; /* position of cursor after end of highlighting */
+ long column1; /* position of column highlight start */
+ long column2; /* position of column highlight end */
+ off_t bracket; /* position of a matching bracket */
+ off_t last_bracket; /* previous position of a matching bracket */
+
+ /* cache speedup for line lookups */
+ gboolean caches_valid;
+ long line_numbers[N_LINE_CACHES];
+ off_t line_offsets[N_LINE_CACHES];
+
+ edit_book_mark_t *book_mark;
+ GArray *serialized_bookmarks;
+
+ /* undo stack and pointers */
+ unsigned long undo_stack_pointer;
+ long *undo_stack;
+ unsigned long undo_stack_size;
+ unsigned long undo_stack_size_mask;
+ unsigned long undo_stack_bottom;
+ unsigned int undo_stack_disable:1; /* If not 0, don't save events in the undo stack */
+
+ unsigned long redo_stack_pointer;
+ long *redo_stack;
+ unsigned long redo_stack_size;
+ unsigned long redo_stack_size_mask;
+ unsigned long redo_stack_bottom;
+ unsigned int redo_stack_reset:1; /* If 1, need clear redo stack */
+
+ struct stat stat1; /* Result of mc_fstat() on the file */
+ unsigned int skip_detach_prompt:1; /* Do not prompt whether to detach a file anymore */
+
+ /* syntax highlighting */
+ GSList *syntax_marker;
+ GPtrArray *rules;
+ off_t last_get_rule;
+ edit_syntax_rule_t rule;
+ char *syntax_type; /* description of syntax highlighting type being used */
+ GTree *defines; /* List of defines */
+ gboolean is_case_insensitive; /* selects language case sensitivity */
+
+ /* line break */
+ LineBreaks lb;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__EDIT_WIDGET_H */
diff --git a/src/editor/etags.c b/src/editor/etags.c
new file mode 100644
index 0000000..7b570d6
--- /dev/null
+++ b/src/editor/etags.c
@@ -0,0 +1,468 @@
+/*
+ Editor C-code navigation via tags.
+ make TAGS file via command:
+ $ find . -type f -name "*.[ch]" | etags -l c --declarations -
+
+ or, if etags utility not installed:
+ $ find . -type f -name "*.[ch]" | ctags --c-kinds=+p --fields=+iaS --extra=+q -e -L-
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+ Slava Zanko <slavazanko@gmail.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2010-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/fileloc.h" /* TAGS_NAME */
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/strutil.h"
+#include "lib/util.h"
+
+#include "editwidget.h"
+
+#include "etags.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static int def_max_width;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+etags_hash_free (gpointer data)
+{
+ etags_hash_t *hash = (etags_hash_t *) data;
+
+ g_free (hash->filename);
+ g_free (hash->fullpath);
+ g_free (hash->short_define);
+ g_free (hash);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+parse_define (const char *buf, char **long_name, char **short_name, long *line)
+{
+ /* *INDENT-OFF* */
+ enum
+ {
+ in_longname,
+ in_shortname,
+ in_shortname_first_char,
+ in_line,
+ finish
+ } def_state = in_longname;
+ /* *INDENT-ON* */
+
+ GString *longdef = NULL;
+ GString *shortdef = NULL;
+ GString *linedef = NULL;
+
+ char c = *buf;
+
+ while (!(c == '\0' || c == '\n'))
+ {
+ switch (def_state)
+ {
+ case in_longname:
+ if (c == 0x01)
+ def_state = in_line;
+ else if (c == 0x7F)
+ def_state = in_shortname;
+ else
+ {
+ if (longdef == NULL)
+ longdef = g_string_sized_new (32);
+
+ g_string_append_c (longdef, c);
+ }
+ break;
+
+ case in_shortname_first_char:
+ if (isdigit (c))
+ {
+ if (shortdef == NULL)
+ shortdef = g_string_sized_new (32);
+ else
+ g_string_set_size (shortdef, 0);
+
+ buf--;
+ def_state = in_line;
+ }
+ else if (c == 0x01)
+ def_state = in_line;
+ else
+ {
+ if (shortdef == NULL)
+ shortdef = g_string_sized_new (32);
+
+ g_string_append_c (shortdef, c);
+ def_state = in_shortname;
+ }
+ break;
+
+ case in_shortname:
+ if (c == 0x01)
+ def_state = in_line;
+ else if (c == '\n')
+ def_state = finish;
+ else
+ {
+ if (shortdef == NULL)
+ shortdef = g_string_sized_new (32);
+
+ g_string_append_c (shortdef, c);
+ }
+ break;
+
+ case in_line:
+ if (c == ',' || c == '\n')
+ def_state = finish;
+ else if (isdigit (c))
+ {
+ if (linedef == NULL)
+ linedef = g_string_sized_new (32);
+
+ g_string_append_c (linedef, c);
+ }
+ break;
+
+ case finish:
+ *long_name = longdef == NULL ? NULL : g_string_free (longdef, FALSE);
+ *short_name = shortdef == NULL ? NULL : g_string_free (shortdef, FALSE);
+
+ if (linedef == NULL)
+ *line = 0;
+ else
+ {
+ *line = atol (linedef->str);
+ g_string_free (linedef, TRUE);
+ }
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ buf++;
+ c = *buf;
+ }
+
+ *long_name = NULL;
+ *short_name = NULL;
+ *line = 0;
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GPtrArray *
+etags_set_definition_hash (const char *tagfile, const char *start_path, const char *match_func)
+{
+ /* *INDENT-OFF* */
+ enum
+ {
+ start,
+ in_filename,
+ in_define
+ } state = start;
+ /* *INDENT-ON* */
+
+ FILE *f;
+ char buf[BUF_LARGE];
+ char *filename = NULL;
+ GPtrArray *ret = NULL;
+
+ if (match_func == NULL || tagfile == NULL)
+ return NULL;
+
+ /* open file with positions */
+ f = fopen (tagfile, "r");
+ if (f == NULL)
+ return NULL;
+
+ while (fgets (buf, sizeof (buf), f) != NULL)
+ switch (state)
+ {
+ case start:
+ if (buf[0] == 0x0C)
+ state = in_filename;
+ break;
+
+ case in_filename:
+ {
+ size_t pos;
+
+ pos = strcspn (buf, ",");
+ g_free (filename);
+ filename = g_strndup (buf, pos);
+ state = in_define;
+ break;
+ }
+
+ case in_define:
+ if (buf[0] == 0x0C)
+ state = in_filename;
+ else
+ {
+ char *chekedstr;
+
+ /* check if the filename matches the define pos */
+ chekedstr = strstr (buf, match_func);
+ if (chekedstr != NULL)
+ {
+ char *longname = NULL;
+ char *shortname = NULL;
+ etags_hash_t *def_hash;
+
+ def_hash = g_new (etags_hash_t, 1);
+
+ def_hash->fullpath = mc_build_filename (start_path, filename, (char *) NULL);
+ def_hash->filename = g_strdup (filename);
+
+ def_hash->line = 0;
+
+ parse_define (chekedstr, &longname, &shortname, &def_hash->line);
+
+ if (shortname != NULL && *shortname != '\0')
+ {
+ def_hash->short_define = shortname;
+ g_free (longname);
+ }
+ else
+ {
+ def_hash->short_define = longname;
+ g_free (shortname);
+ }
+
+ if (ret == NULL)
+ ret = g_ptr_array_new_with_free_func (etags_hash_free);
+
+ g_ptr_array_add (ret, def_hash);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ g_free (filename);
+ fclose (f);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+editcmd_dialog_select_definition_add (gpointer data, gpointer user_data)
+{
+ etags_hash_t *def_hash = (etags_hash_t *) data;
+ WListbox *def_list = (WListbox *) user_data;
+ char *label_def;
+ int def_width;
+
+ label_def =
+ g_strdup_printf ("%s -> %s:%ld", def_hash->short_define, def_hash->filename,
+ def_hash->line);
+ listbox_add_item (def_list, LISTBOX_APPEND_AT_END, 0, label_def, def_hash, FALSE);
+ def_width = str_term_width1 (label_def);
+ g_free (label_def);
+ def_max_width = MAX (def_max_width, def_width);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* let the user select where function definition */
+
+static void
+editcmd_dialog_select_definition_show (WEdit * edit, char *match_expr, GPtrArray * def_hash)
+{
+ const WRect *w = &CONST_WIDGET (edit)->rect;
+ int start_x, start_y, offset;
+ char *curr = NULL;
+ WDialog *def_dlg;
+ WListbox *def_list;
+ int def_dlg_h; /* dialog height */
+ int def_dlg_w; /* dialog width */
+
+ /* calculate the dialog metrics */
+ def_dlg_h = def_hash->len + 2;
+ def_dlg_w = COLS - 2; /* will be clarified later */
+ start_x = w->x + edit->curs_col + edit->start_col + EDIT_TEXT_HORIZONTAL_OFFSET +
+ (edit->fullscreen ? 0 : 1) + edit_options.line_state_width;
+ start_y = w->y + edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + (edit->fullscreen ? 0 : 1) + 1;
+
+ if (start_x < 0)
+ start_x = 0;
+ if (start_x < w->x + 1)
+ start_x = w->x + 1 + edit_options.line_state_width;
+
+ if (def_dlg_h > LINES - 2)
+ def_dlg_h = LINES - 2;
+
+ offset = start_y + def_dlg_h - LINES;
+ if (offset > 0)
+ start_y -= (offset + 1);
+
+ def_dlg = dlg_create (TRUE, start_y, start_x, def_dlg_h, def_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, NULL, NULL, "[Definitions]", match_expr);
+ def_list = listbox_new (1, 1, def_dlg_h - 2, def_dlg_w - 2, FALSE, NULL);
+ group_add_widget_autopos (GROUP (def_dlg), def_list, WPOS_KEEP_ALL, NULL);
+
+ /* fill the listbox with the completions and get the maximum width */
+ def_max_width = 0;
+ g_ptr_array_foreach (def_hash, editcmd_dialog_select_definition_add, def_list);
+
+ /* adjust dialog width */
+ def_dlg_w = def_max_width + 4;
+ offset = start_x + def_dlg_w - COLS;
+ if (offset > 0)
+ start_x -= offset;
+
+ widget_set_size (WIDGET (def_dlg), start_y, start_x, def_dlg_h, def_dlg_w);
+
+ /* pop up the dialog and apply the chosen completion */
+ if (dlg_run (def_dlg) == B_ENTER)
+ {
+ etags_hash_t *curr_def = NULL;
+ gboolean do_moveto = FALSE;
+
+ listbox_get_current (def_list, &curr, (void **) &curr_def);
+
+ if (!edit->modified)
+ do_moveto = TRUE;
+ else if (!edit_query_dialog2
+ (_("Warning"),
+ _("Current text was modified without a file save.\n"
+ "Continue discards these changes."), _("C&ontinue"), _("&Cancel")))
+ {
+ edit->force |= REDRAW_COMPLETELY;
+ do_moveto = TRUE;
+ }
+
+ if (curr != NULL && do_moveto && edit_stack_iterator + 1 < MAX_HISTORY_MOVETO)
+ {
+ vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
+
+ /* Is file path absolute? Prepend with dir_vpath if necessary */
+ if (edit->filename_vpath != NULL && edit->filename_vpath->relative
+ && edit->dir_vpath != NULL)
+ edit_history_moveto[edit_stack_iterator].filename_vpath =
+ vfs_path_append_vpath_new (edit->dir_vpath, edit->filename_vpath, NULL);
+ else
+ edit_history_moveto[edit_stack_iterator].filename_vpath =
+ vfs_path_clone (edit->filename_vpath);
+
+ edit_history_moveto[edit_stack_iterator].line = edit->start_line + edit->curs_row + 1;
+ edit_stack_iterator++;
+ vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
+ edit_history_moveto[edit_stack_iterator].filename_vpath =
+ vfs_path_from_str ((char *) curr_def->fullpath);
+ edit_history_moveto[edit_stack_iterator].line = curr_def->line;
+ edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
+ edit_history_moveto[edit_stack_iterator].line);
+ }
+ }
+
+ /* destroy dialog before return */
+ widget_destroy (WIDGET (def_dlg));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_get_match_keyword_cmd (WEdit * edit)
+{
+ gsize word_len = 0;
+ gsize i;
+ off_t word_start = 0;
+ GString *match_expr;
+ char *path = NULL;
+ char *ptr = NULL;
+ char *tagfile = NULL;
+ GPtrArray *def_hash = NULL;
+
+ /* search start of word to be completed */
+ if (!edit_buffer_find_word_start (&edit->buffer, &word_start, &word_len))
+ return;
+
+ /* prepare match expression */
+ match_expr = g_string_sized_new (word_len);
+ for (i = 0; i < word_len; i++)
+ g_string_append_c (match_expr, edit_buffer_get_byte (&edit->buffer, word_start + i));
+
+ ptr = g_get_current_dir ();
+ path = g_strconcat (ptr, PATH_SEP_STR, (char *) NULL);
+ g_free (ptr);
+
+ /* Recursive search file 'TAGS' in parent dirs */
+ do
+ {
+ ptr = g_path_get_dirname (path);
+ g_free (path);
+ path = ptr;
+ g_free (tagfile);
+ tagfile = mc_build_filename (path, TAGS_NAME, (char *) NULL);
+ if (tagfile != NULL && exist_file (tagfile))
+ break;
+ }
+ while (strcmp (path, PATH_SEP_STR) != 0);
+
+ if (tagfile != NULL)
+ {
+ def_hash = etags_set_definition_hash (tagfile, path, match_expr->str);
+ g_free (tagfile);
+ }
+ g_free (path);
+
+ if (def_hash != NULL)
+ {
+ editcmd_dialog_select_definition_show (edit, match_expr->str, def_hash);
+
+ g_ptr_array_free (def_hash, TRUE);
+ }
+
+ g_string_free (match_expr, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/etags.h b/src/editor/etags.h
new file mode 100644
index 0000000..23813e9
--- /dev/null
+++ b/src/editor/etags.h
@@ -0,0 +1,26 @@
+#ifndef MC__EDIT_ETAGS_H
+#define MC__EDIT_ETAGS_H 1
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct etags_hash_struct
+{
+ char *filename;
+ char *fullpath;
+ char *short_define;
+ long line;
+} etags_hash_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void edit_get_match_keyword_cmd (WEdit * edit);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EDIT_ETAGS_H */
diff --git a/src/editor/format.c b/src/editor/format.c
new file mode 100644
index 0000000..3193067
--- /dev/null
+++ b/src/editor/format.c
@@ -0,0 +1,538 @@
+/*
+ Dynamic paragraph formatting.
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Copyright (C) 1996 Paul Sheer
+
+ Written by:
+ Paul Sheer, 1996
+ Andrew Borodin <aborodin@vmail.ru>, 2013, 2014
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: Dynamic paragraph formatting
+ * \author Paul Sheer
+ * \date 1996
+ * \author Andrew Borodin
+ * \date 2013, 2014
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/util.h" /* whitespace() */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define FONT_MEAN_WIDTH 1
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+line_start (const edit_buffer_t * buf, long line)
+{
+ off_t p;
+ long l;
+
+ l = buf->curs_line;
+ p = buf->curs1;
+
+ if (line < l)
+ p = edit_buffer_get_backward_offset (buf, p, l - line);
+ else if (line > l)
+ p = edit_buffer_get_forward_offset (buf, p, line - l, 0);
+
+ p = edit_buffer_get_bol (buf, p);
+ while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL)
+ p++;
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+bad_line_start (const edit_buffer_t * buf, off_t p)
+{
+ int c;
+
+ c = edit_buffer_get_byte (buf, p);
+ if (c == '.')
+ {
+ /* `...' is acceptable */
+ return !(edit_buffer_get_byte (buf, p + 1) == '.'
+ && edit_buffer_get_byte (buf, p + 2) == '.');
+ }
+ if (c == '-')
+ {
+ /* `---' is acceptable */
+ return !(edit_buffer_get_byte (buf, p + 1) == '-'
+ && edit_buffer_get_byte (buf, p + 2) == '-');
+ }
+
+ return (edit_options.stop_format_chars != NULL
+ && strchr (edit_options.stop_format_chars, c) != NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find the start of the current paragraph for the purpose of formatting.
+ * Return position in the file.
+ */
+
+static off_t
+begin_paragraph (WEdit * edit, gboolean force, long *lines)
+{
+ long i;
+
+ for (i = edit->buffer.curs_line - 1; i >= 0; i--)
+ if (edit_line_is_blank (edit, i) ||
+ (force && bad_line_start (&edit->buffer, line_start (&edit->buffer, i))))
+ {
+ i++;
+ break;
+ }
+
+ *lines = edit->buffer.curs_line - i;
+
+ return edit_buffer_get_backward_offset (&edit->buffer,
+ edit_buffer_get_current_bol (&edit->buffer), *lines);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find the end of the current paragraph for the purpose of formatting.
+ * Return position in the file.
+ */
+
+static off_t
+end_paragraph (WEdit * edit, gboolean force)
+{
+ long i;
+
+ for (i = edit->buffer.curs_line + 1; i <= edit->buffer.lines; i++)
+ if (edit_line_is_blank (edit, i) ||
+ (force && bad_line_start (&edit->buffer, line_start (&edit->buffer, i))))
+ {
+ i--;
+ break;
+ }
+
+ return edit_buffer_get_eol (&edit->buffer,
+ edit_buffer_get_forward_offset (&edit->buffer,
+ edit_buffer_get_current_bol
+ (&edit->buffer),
+ i - edit->buffer.curs_line, 0));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+get_paragraph (const edit_buffer_t * buf, off_t p, off_t q, gboolean indent)
+{
+ GString *t;
+
+ t = g_string_sized_new (128);
+
+ for (; p < q; p++)
+ {
+ if (indent && edit_buffer_get_byte (buf, p - 1) == '\n')
+ while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL)
+ p++;
+
+ g_string_append_c (t, edit_buffer_get_byte (buf, p));
+ }
+
+ g_string_append_c (t, '\n');
+
+ return t;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+strip_newlines (unsigned char *t, off_t size)
+{
+ unsigned char *p;
+
+ for (p = t; size-- != 0; p++)
+ if (*p == '\n')
+ *p = ' ';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ This function calculates the number of chars in a line specified to length l in pixels
+ */
+
+static inline off_t
+next_tab_pos (off_t x)
+{
+ x += TAB_SIZE - x % TAB_SIZE;
+ return x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline off_t
+line_pixel_length (unsigned char *t, off_t b, off_t l, gboolean utf8)
+{
+ off_t xn, x; /* position counters */
+ off_t char_length = 0; /* character length in bytes */
+
+#ifndef HAVE_CHARSET
+ (void) utf8;
+#endif
+
+ for (xn = 0, x = 0; xn <= l; x = xn)
+ {
+ char *tb;
+
+ b += char_length;
+ tb = (char *) t + b;
+ char_length = 1;
+
+ switch (tb[0])
+ {
+ case '\n':
+ return b;
+ case '\t':
+ xn = next_tab_pos (x);
+ break;
+ default:
+#ifdef HAVE_CHARSET
+ if (utf8)
+ {
+ gunichar ch;
+
+ ch = g_utf8_get_char_validated (tb, -1);
+ if (ch != (gunichar) (-2) && ch != (gunichar) (-1))
+ {
+ char *next_ch;
+
+ /* Calculate UTF-8 char length */
+ next_ch = g_utf8_next_char (tb);
+ char_length = next_ch - tb;
+
+ if (g_unichar_iswide (ch))
+ x++;
+ }
+ }
+#endif
+
+ xn = x + 1;
+ break;
+ }
+ }
+
+ return b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+next_word_start (unsigned char *t, off_t q, off_t size)
+{
+ off_t i;
+ gboolean saw_ws = FALSE;
+
+ for (i = q; i < size; i++)
+ {
+ switch (t[i])
+ {
+ case '\n':
+ return -1;
+ case '\t':
+ case ' ':
+ saw_ws = TRUE;
+ break;
+ default:
+ if (saw_ws)
+ return i;
+ break;
+ }
+ }
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** find the start of a word */
+
+static inline int
+word_start (unsigned char *t, off_t q, off_t size)
+{
+ off_t i;
+
+ if (whitespace (t[q]))
+ return next_word_start (t, q, size);
+
+ for (i = q;; i--)
+ {
+ unsigned char c;
+
+ if (i == 0)
+ return (-1);
+ c = t[i - 1];
+ if (c == '\n')
+ return (-1);
+ if (whitespace (c))
+ return i;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** replaces ' ' with '\n' to properly format a paragraph */
+
+static inline void
+format_this (unsigned char *t, off_t size, long indent, gboolean utf8)
+{
+ off_t q = 0, ww;
+
+ strip_newlines (t, size);
+ ww = edit_options.word_wrap_line_length * FONT_MEAN_WIDTH - indent;
+ if (ww < FONT_MEAN_WIDTH * 2)
+ ww = FONT_MEAN_WIDTH * 2;
+
+ while (TRUE)
+ {
+ off_t p;
+
+ q = line_pixel_length (t, q, ww, utf8);
+ if (q > size)
+ break;
+ if (t[q] == '\n')
+ break;
+ p = word_start (t, q, size);
+ if (p == -1)
+ q = next_word_start (t, q, size); /* Return the end of the word if the beginning
+ of the word is at the beginning of a line
+ (i.e. a very long word) */
+ else
+ q = p;
+ if (q == -1) /* end of paragraph */
+ break;
+ if (q != 0)
+ t[q - 1] = '\n';
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+replace_at (WEdit * edit, off_t q, int c)
+{
+ edit_cursor_move (edit, q - edit->buffer.curs1);
+ edit_delete (edit, TRUE);
+ edit_insert_ahead (edit, c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+edit_indent_width (const WEdit * edit, off_t p)
+{
+ off_t q = p;
+
+ /* move to the end of the leading whitespace of the line */
+ while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, q)) != NULL
+ && q < edit->buffer.size - 1)
+ q++;
+ /* count the number of columns of indentation */
+ return (long) edit_move_forward3 (edit, p, 0, q);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_insert_indent (WEdit * edit, long indent)
+{
+ if (!edit_options.fill_tabs_with_spaces)
+ while (indent >= TAB_SIZE)
+ {
+ edit_insert (edit, '\t');
+ indent -= TAB_SIZE;
+ }
+
+ while (indent-- > 0)
+ edit_insert (edit, ' ');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** replaces a block of text */
+
+static inline void
+put_paragraph (WEdit * edit, unsigned char *t, off_t p, long indent, off_t size)
+{
+ off_t cursor;
+ off_t i;
+ int c = '\0';
+
+ cursor = edit->buffer.curs1;
+ if (indent != 0)
+ while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
+ p++;
+ for (i = 0; i < size; i++, p++)
+ {
+ if (i != 0 && indent != 0)
+ {
+ if (t[i - 1] == '\n' && c == '\n')
+ {
+ while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
+ p++;
+ }
+ else if (t[i - 1] == '\n')
+ {
+ off_t curs;
+
+ edit_cursor_move (edit, p - edit->buffer.curs1);
+ curs = edit->buffer.curs1;
+ edit_insert_indent (edit, indent);
+ if (cursor >= curs)
+ cursor += edit->buffer.curs1 - p;
+ p = edit->buffer.curs1;
+ }
+ else if (c == '\n')
+ {
+ edit_cursor_move (edit, p - edit->buffer.curs1);
+ while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
+ {
+ edit_delete (edit, TRUE);
+ if (cursor > edit->buffer.curs1)
+ cursor--;
+ }
+ p = edit->buffer.curs1;
+ }
+ }
+
+ c = edit_buffer_get_byte (&edit->buffer, p);
+ if (c != t[i])
+ replace_at (edit, p, t[i]);
+ }
+ edit_cursor_move (edit, cursor - edit->buffer.curs1); /* restore cursor position */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline long
+test_indent (const WEdit * edit, off_t p, off_t q)
+{
+ long indent;
+
+ indent = edit_indent_width (edit, p++);
+ if (indent == 0)
+ return 0;
+
+ for (; p < q; p++)
+ if (edit_buffer_get_byte (&edit->buffer, p - 1) == '\n'
+ && indent != edit_indent_width (edit, p))
+ return 0;
+ return indent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+format_paragraph (WEdit * edit, gboolean force)
+{
+ off_t p, q;
+ long lines;
+ off_t size;
+ GString *t;
+ long indent;
+ unsigned char *t2;
+ gboolean utf8 = FALSE;
+
+ if (edit_options.word_wrap_line_length < 2)
+ return;
+ if (edit_line_is_blank (edit, edit->buffer.curs_line))
+ return;
+
+ p = begin_paragraph (edit, force, &lines);
+ q = end_paragraph (edit, force);
+ indent = test_indent (edit, p, q);
+
+ t = get_paragraph (&edit->buffer, p, q, indent != 0);
+ size = t->len - 1;
+
+ if (!force)
+ {
+ off_t i;
+ char *stop_format_chars;
+
+ if (edit_options.stop_format_chars != NULL
+ && strchr (edit_options.stop_format_chars, t->str[0]) != NULL)
+ {
+ g_string_free (t, TRUE);
+ return;
+ }
+
+ if (edit_options.stop_format_chars == NULL || *edit_options.stop_format_chars == '\0')
+ stop_format_chars = g_strdup ("\t");
+ else
+ stop_format_chars = g_strconcat (edit_options.stop_format_chars, "\t", (char *) NULL);
+
+ for (i = 0; i < size - 1; i++)
+ if (t->str[i] == '\n' && strchr (stop_format_chars, t->str[i + 1]) != NULL)
+ {
+ g_free (stop_format_chars);
+ g_string_free (t, TRUE);
+ return;
+ }
+
+ g_free (stop_format_chars);
+ }
+
+ t2 = (unsigned char *) g_string_free (t, FALSE);
+#ifdef HAVE_CHARSET
+ utf8 = edit->utf8;
+#endif
+ format_this (t2, q - p, indent, utf8);
+ put_paragraph (edit, t2, p, indent, size);
+ g_free ((char *) t2);
+
+ /* Scroll left as much as possible to show the formatted paragraph */
+ edit_scroll_left (edit, -edit->start_col);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/spell.c b/src/editor/spell.c
new file mode 100644
index 0000000..aeb0884
--- /dev/null
+++ b/src/editor/spell.c
@@ -0,0 +1,834 @@
+/*
+ Editor spell checker
+
+ Copyright (C) 2012-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2012
+ Andrew Borodin <aborodin@vmail.ru>, 2013, 2021
+
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <gmodule.h>
+#include <aspell.h>
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+#include "lib/strutil.h"
+#include "lib/util.h" /* MC_PTR_FREE() */
+#include "lib/tty/tty.h" /* COLS, LINES */
+
+#include "src/setup.h"
+
+#include "editwidget.h"
+
+#include "spell.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define B_SKIP_WORD (B_USER+3)
+#define B_ADD_WORD (B_USER+4)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct aspell_struct
+{
+ AspellConfig *config;
+ AspellSpeller *speller;
+} spell_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static GModule *spell_module = NULL;
+static spell_t *global_speller = NULL;
+
+static AspellConfig *(*mc_new_aspell_config) (void);
+static int (*mc_aspell_config_replace) (AspellConfig * ths, const char *key, const char *value);
+static AspellCanHaveError *(*mc_new_aspell_speller) (AspellConfig * config);
+static unsigned int (*mc_aspell_error_number) (const AspellCanHaveError * ths);
+static const char *(*mc_aspell_speller_error_message) (const AspellSpeller * ths);
+static const AspellError *(*mc_aspell_speller_error) (const AspellSpeller * ths);
+
+static AspellSpeller *(*mc_to_aspell_speller) (AspellCanHaveError * obj);
+static int (*mc_aspell_speller_check) (AspellSpeller * ths, const char *word, int word_size);
+static const AspellWordList *(*mc_aspell_speller_suggest) (AspellSpeller * ths,
+ const char *word, int word_size);
+static AspellStringEnumeration *(*mc_aspell_word_list_elements) (const struct AspellWordList * ths);
+static const char *(*mc_aspell_config_retrieve) (AspellConfig * ths, const char *key);
+static void (*mc_delete_aspell_speller) (AspellSpeller * ths);
+static void (*mc_delete_aspell_config) (AspellConfig * ths);
+static void (*mc_delete_aspell_can_have_error) (AspellCanHaveError * ths);
+static const char *(*mc_aspell_error_message) (const AspellCanHaveError * ths);
+static void (*mc_delete_aspell_string_enumeration) (AspellStringEnumeration * ths);
+static AspellDictInfoEnumeration *(*mc_aspell_dict_info_list_elements)
+ (const AspellDictInfoList * ths);
+static AspellDictInfoList *(*mc_get_aspell_dict_info_list) (AspellConfig * config);
+static const AspellDictInfo *(*mc_aspell_dict_info_enumeration_next)
+ (AspellDictInfoEnumeration * ths);
+static const char *(*mc_aspell_string_enumeration_next) (AspellStringEnumeration * ths);
+static void (*mc_delete_aspell_dict_info_enumeration) (AspellDictInfoEnumeration * ths);
+static unsigned int (*mc_aspell_word_list_size) (const AspellWordList * ths);
+static const AspellError *(*mc_aspell_error) (const AspellCanHaveError * ths);
+static int (*mc_aspell_speller_add_to_personal) (AspellSpeller * ths, const char *word,
+ int word_size);
+static int (*mc_aspell_speller_save_all_word_lists) (AspellSpeller * ths);
+
+static struct
+{
+ const char *code;
+ const char *name;
+} spell_codes_map[] =
+{
+ /* *INDENT-OFF* */
+ {"br", N_("Breton")},
+ {"cs", N_("Czech")},
+ {"cy", N_("Welsh")},
+ {"da", N_("Danish")},
+ {"de", N_("German")},
+ {"el", N_("Greek")},
+ {"en", N_("English")},
+ {"en_GB", N_("British English")},
+ {"en_CA", N_("Canadian English")},
+ {"en_US", N_("American English")},
+ {"eo", N_("Esperanto")},
+ {"es", N_("Spanish")},
+ {"fo", N_("Faroese")},
+ {"fr", N_("French")},
+ {"it", N_("Italian")},
+ {"nl", N_("Dutch")},
+ {"no", N_("Norwegian")},
+ {"pl", N_("Polish")},
+ {"pt", N_("Portuguese")},
+ {"ro", N_("Romanian")},
+ {"ru", N_("Russian")},
+ {"sk", N_("Slovak")},
+ {"sv", N_("Swedish")},
+ {"uk", N_("Ukrainian")},
+ {NULL, NULL}
+ /* *INDENT-ON* */
+};
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Found the language name by language code. For example: en_US -> American English.
+ *
+ * @param code Short name of the language (ru, en, pl, uk, etc...)
+ * @return language name
+ */
+
+static const char *
+spell_decode_lang (const char *code)
+{
+ size_t i;
+
+ for (i = 0; spell_codes_map[i].code != NULL; i++)
+ {
+ if (strcmp (spell_codes_map[i].code, code) == 0)
+ return _(spell_codes_map[i].name);
+ }
+
+ return code;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Checks if aspell library and symbols are available.
+ *
+ * @return FALSE or error
+ */
+
+static gboolean
+spell_available (void)
+{
+ gchar *spell_module_fname;
+ gboolean ret = FALSE;
+
+ if (spell_module != NULL)
+ return TRUE;
+
+ spell_module_fname = g_module_build_path (NULL, "libaspell");
+ spell_module = g_module_open (spell_module_fname, G_MODULE_BIND_LAZY);
+
+ g_free (spell_module_fname);
+
+ if (spell_module == NULL)
+ return FALSE;
+
+ if (!g_module_symbol (spell_module, "new_aspell_config", (void *) &mc_new_aspell_config))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_dict_info_list_elements",
+ (void *) &mc_aspell_dict_info_list_elements))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_dict_info_enumeration_next",
+ (void *) &mc_aspell_dict_info_enumeration_next))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "new_aspell_speller", (void *) &mc_new_aspell_speller))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_error_number", (void *) &mc_aspell_error_number))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_speller_error_message",
+ (void *) &mc_aspell_speller_error_message))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_speller_error", (void *) &mc_aspell_speller_error))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_error", (void *) &mc_aspell_error))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "to_aspell_speller", (void *) &mc_to_aspell_speller))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_speller_check", (void *) &mc_aspell_speller_check))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "aspell_speller_suggest", (void *) &mc_aspell_speller_suggest))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "aspell_word_list_elements", (void *) &mc_aspell_word_list_elements))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_string_enumeration_next",
+ (void *) &mc_aspell_string_enumeration_next))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "aspell_config_replace", (void *) &mc_aspell_config_replace))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_error_message", (void *) &mc_aspell_error_message))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "delete_aspell_speller", (void *) &mc_delete_aspell_speller))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "delete_aspell_config", (void *) &mc_delete_aspell_config))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "delete_aspell_string_enumeration",
+ (void *) &mc_delete_aspell_string_enumeration))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "get_aspell_dict_info_list",
+ (void *) &mc_get_aspell_dict_info_list))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "delete_aspell_can_have_error",
+ (void *) &mc_delete_aspell_can_have_error))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "delete_aspell_dict_info_enumeration",
+ (void *) &mc_delete_aspell_dict_info_enumeration))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "aspell_config_retrieve", (void *) &mc_aspell_config_retrieve))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "aspell_word_list_size", (void *) &mc_aspell_word_list_size))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_speller_add_to_personal",
+ (void *) &mc_aspell_speller_add_to_personal))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_speller_save_all_word_lists",
+ (void *) &mc_aspell_speller_save_all_word_lists))
+ goto error_ret;
+
+ ret = TRUE;
+
+ error_ret:
+ if (!ret)
+ {
+ g_module_close (spell_module);
+ spell_module = NULL;
+ }
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialization of Aspell support.
+ */
+
+void
+aspell_init (void)
+{
+ AspellCanHaveError *error = NULL;
+
+ if (strcmp (spell_language, "NONE") == 0)
+ return;
+
+ if (global_speller != NULL)
+ return;
+
+ global_speller = g_try_malloc (sizeof (spell_t));
+ if (global_speller == NULL)
+ return;
+
+ if (!spell_available ())
+ {
+ MC_PTR_FREE (global_speller);
+ return;
+ }
+
+ global_speller->config = mc_new_aspell_config ();
+ global_speller->speller = NULL;
+
+ if (spell_language != NULL)
+ mc_aspell_config_replace (global_speller->config, "lang", spell_language);
+
+ error = mc_new_aspell_speller (global_speller->config);
+
+ if (mc_aspell_error_number (error) == 0)
+ global_speller->speller = mc_to_aspell_speller (error);
+ else
+ {
+ edit_error_dialog (_("Error"), mc_aspell_error_message (error));
+ mc_delete_aspell_can_have_error (error);
+ aspell_clean ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deinitialization of Aspell support.
+ */
+
+void
+aspell_clean (void)
+{
+ if (global_speller == NULL)
+ return;
+
+ if (global_speller->speller != NULL)
+ mc_delete_aspell_speller (global_speller->speller);
+
+ if (global_speller->config != NULL)
+ mc_delete_aspell_config (global_speller->config);
+
+ MC_PTR_FREE (global_speller);
+
+ g_module_close (spell_module);
+ spell_module = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get array of available languages.
+ *
+ * @param lang_list Array of languages. Must be cleared before use
+ * @return language list length
+ */
+
+unsigned int
+aspell_get_lang_list (GPtrArray * lang_list)
+{
+ AspellDictInfoList *dlist;
+ AspellDictInfoEnumeration *elem;
+ const AspellDictInfo *entry;
+ unsigned int i = 0;
+
+ if (spell_module == NULL)
+ return 0;
+
+ /* the returned pointer should _not_ need to be deleted */
+ dlist = mc_get_aspell_dict_info_list (global_speller->config);
+ elem = mc_aspell_dict_info_list_elements (dlist);
+
+ while ((entry = mc_aspell_dict_info_enumeration_next (elem)) != NULL)
+ if (entry->name != NULL)
+ {
+ g_ptr_array_add (lang_list, g_strdup (entry->name));
+ i++;
+ }
+
+ mc_delete_aspell_dict_info_enumeration (elem);
+
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Clear the array of languages.
+ *
+ * @param array Array of languages
+ */
+
+void
+aspell_array_clean (GPtrArray * array)
+{
+ if (array != NULL)
+ g_ptr_array_free (array, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get the current language name.
+ *
+ * @return language name
+ */
+
+const char *
+aspell_get_lang (void)
+{
+ const char *code;
+
+ code = mc_aspell_config_retrieve (global_speller->config, "lang");
+ return spell_decode_lang (code);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set the language.
+ *
+ * @param lang Language name
+ * @return FALSE or error
+ */
+
+gboolean
+aspell_set_lang (const char *lang)
+{
+ if (lang != NULL)
+ {
+ AspellCanHaveError *error;
+ const char *spell_codeset;
+
+ g_free (spell_language);
+ spell_language = g_strdup (lang);
+
+#ifdef HAVE_CHARSET
+ if (mc_global.source_codepage > 0)
+ spell_codeset = get_codepage_id (mc_global.source_codepage);
+ else
+#endif
+ spell_codeset = str_detect_termencoding ();
+
+ mc_aspell_config_replace (global_speller->config, "lang", lang);
+ mc_aspell_config_replace (global_speller->config, "encoding", spell_codeset);
+
+ /* the returned pointer should _not_ need to be deleted */
+ if (global_speller->speller != NULL)
+ mc_delete_aspell_speller (global_speller->speller);
+
+ global_speller->speller = NULL;
+
+ error = mc_new_aspell_speller (global_speller->config);
+ if (mc_aspell_error (error) != 0)
+ {
+ mc_delete_aspell_can_have_error (error);
+ return FALSE;
+ }
+
+ global_speller->speller = mc_to_aspell_speller (error);
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check word.
+ *
+ * @param word Word for spell check
+ * @param word_size Word size (in bytes)
+ * @return FALSE if word is not in the dictionary
+ */
+
+gboolean
+aspell_check (const char *word, const int word_size)
+{
+ int res = 0;
+
+ if (word != NULL && global_speller != NULL && global_speller->speller != NULL)
+ res = mc_aspell_speller_check (global_speller->speller, word, word_size);
+
+ return (res == 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Examine dictionaries and suggest possible words that may repalce the incorrect word.
+ *
+ * @param suggest array of words to iterate through
+ * @param word Word for spell check
+ * @param word_size Word size (in bytes)
+ * @return count of suggests for the word
+ */
+
+unsigned int
+aspell_suggest (GPtrArray * suggest, const char *word, const int word_size)
+{
+ unsigned int size = 0;
+
+ if (word != NULL && global_speller != NULL && global_speller->speller != NULL)
+ {
+ const AspellWordList *wordlist;
+
+ wordlist = mc_aspell_speller_suggest (global_speller->speller, word, word_size);
+ if (wordlist != NULL)
+ {
+ AspellStringEnumeration *elements = NULL;
+ unsigned int i;
+
+ elements = mc_aspell_word_list_elements (wordlist);
+ size = mc_aspell_word_list_size (wordlist);
+
+ for (i = 0; i < size; i++)
+ {
+ const char *cur_sugg_word;
+
+ cur_sugg_word = mc_aspell_string_enumeration_next (elements);
+ if (cur_sugg_word != NULL)
+ g_ptr_array_add (suggest, g_strdup (cur_sugg_word));
+ }
+
+ mc_delete_aspell_string_enumeration (elements);
+ }
+ }
+
+ return size;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Add word to personal dictionary.
+ *
+ * @param word Word for spell check
+ * @param word_size Word size (in bytes)
+ * @return FALSE or error
+ */
+gboolean
+aspell_add_to_dict (const char *word, int word_size)
+{
+ mc_aspell_speller_add_to_personal (global_speller->speller, word, word_size);
+
+ if (mc_aspell_speller_error (global_speller->speller) != 0)
+ {
+ edit_error_dialog (_("Error"), mc_aspell_speller_error_message (global_speller->speller));
+ return FALSE;
+ }
+
+ mc_aspell_speller_save_all_word_lists (global_speller->speller);
+
+ if (mc_aspell_speller_error (global_speller->speller) != 0)
+ {
+ edit_error_dialog (_("Error"), mc_aspell_speller_error_message (global_speller->speller));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+edit_suggest_current_word (WEdit * edit)
+{
+ gsize cut_len = 0;
+ gsize word_len = 0;
+ off_t word_start = 0;
+ int retval = B_SKIP_WORD;
+ GString *match_word;
+
+ /* search start of word to spell check */
+ match_word = edit_buffer_get_word_from_pos (&edit->buffer, edit->buffer.curs1, &word_start,
+ &cut_len);
+ word_len = match_word->len;
+
+#ifdef HAVE_CHARSET
+ if (mc_global.source_codepage >= 0 && mc_global.source_codepage != mc_global.display_codepage)
+ {
+ GString *tmp_word;
+
+ tmp_word = str_convert_to_display (match_word->str);
+ g_string_free (match_word, TRUE);
+ match_word = tmp_word;
+ }
+#endif
+ if (match_word != NULL)
+ {
+ if (!aspell_check (match_word->str, (int) word_len))
+ {
+ GPtrArray *suggest;
+ unsigned int res;
+ guint i;
+
+ suggest = g_ptr_array_new_with_free_func (g_free);
+
+ res = aspell_suggest (suggest, match_word->str, (int) word_len);
+ if (res != 0)
+ {
+ char *new_word = NULL;
+
+ edit->found_start = word_start;
+ edit->found_len = word_len;
+ edit->force |= REDRAW_PAGE;
+ edit_scroll_screen_over_cursor (edit);
+ edit_render_keypress (edit);
+
+ retval =
+ spell_dialog_spell_suggest_show (edit, match_word->str, &new_word, suggest);
+ edit_cursor_move (edit, word_len - cut_len);
+
+ if (retval == B_ENTER && new_word != NULL)
+ {
+#ifdef HAVE_CHARSET
+ if (mc_global.source_codepage >= 0 &&
+ (mc_global.source_codepage != mc_global.display_codepage))
+ {
+ GString *tmp_word;
+
+ tmp_word = str_convert_to_input (new_word);
+ MC_PTR_FREE (new_word);
+ if (tmp_word != NULL)
+ new_word = g_string_free (tmp_word, FALSE);
+ }
+#endif
+ for (i = 0; i < word_len; i++)
+ edit_backspace (edit, TRUE);
+ if (new_word != NULL)
+ {
+ for (i = 0; new_word[i] != '\0'; i++)
+ edit_insert (edit, new_word[i]);
+ g_free (new_word);
+ }
+ }
+ else if (retval == B_ADD_WORD)
+ aspell_add_to_dict (match_word->str, (int) word_len);
+ }
+
+ g_ptr_array_free (suggest, TRUE);
+ edit->found_start = 0;
+ edit->found_len = 0;
+ }
+
+ g_string_free (match_word, TRUE);
+ }
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_spellcheck_file (WEdit * edit)
+{
+ if (edit->buffer.curs_line > 0)
+ {
+ edit_cursor_move (edit, -edit->buffer.curs1);
+ edit_move_to_prev_col (edit, 0);
+ edit_update_curs_row (edit);
+ }
+
+ do
+ {
+ int c1, c2;
+
+ c2 = edit_buffer_get_current_byte (&edit->buffer);
+
+ do
+ {
+ if (edit->buffer.curs1 >= edit->buffer.size)
+ return;
+
+ c1 = c2;
+ edit_cursor_move (edit, 1);
+ c2 = edit_buffer_get_current_byte (&edit->buffer);
+ }
+ while (is_break_char (c1) || is_break_char (c2));
+ }
+ while (edit_suggest_current_word (edit) != B_CANCEL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_set_spell_lang (void)
+{
+ GPtrArray *lang_list;
+
+ lang_list = g_ptr_array_new_with_free_func (g_free);
+ if (aspell_get_lang_list (lang_list) != 0)
+ {
+ const char *lang;
+
+ lang = spell_dialog_lang_list_show (lang_list);
+ if (lang != NULL)
+ (void) aspell_set_lang (lang);
+ }
+ aspell_array_clean (lang_list);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show suggests for the current word.
+ *
+ * @param edit Editor object
+ * @param word Word for spell check
+ * @param new_word Word to replace the incorrect word
+ * @param suggest Array of suggests for current word
+ * @return code of pressed button
+ */
+
+int
+spell_dialog_spell_suggest_show (WEdit * edit, const char *word, char **new_word,
+ const GPtrArray * suggest)
+{
+
+ int sug_dlg_h = 14; /* dialog height */
+ int sug_dlg_w = 29; /* dialog width */
+ int xpos, ypos;
+ char *lang_label;
+ char *word_label;
+ unsigned int i;
+ int res;
+ char *curr = NULL;
+ WDialog *sug_dlg;
+ WGroup *g;
+ WListbox *sug_list;
+ int max_btn_len = 0;
+ int replace_len;
+ int skip_len;
+ int cancel_len;
+ WButton *add_btn;
+ WButton *replace_btn;
+ WButton *skip_btn;
+ WButton *cancel_button;
+ int word_label_len;
+
+ /* calculate the dialog metrics */
+ xpos = (COLS - sug_dlg_w) / 2;
+ ypos = (LINES - sug_dlg_h) * 2 / 3;
+
+ /* Sometimes menu can hide replaced text. I don't like it */
+ if ((edit->curs_row >= ypos - 1) && (edit->curs_row <= ypos + sug_dlg_h - 1))
+ ypos -= sug_dlg_h;
+
+ add_btn = button_new (5, 28, B_ADD_WORD, NORMAL_BUTTON, _("&Add word"), 0);
+ replace_btn = button_new (7, 28, B_ENTER, NORMAL_BUTTON, _("&Replace"), 0);
+ replace_len = button_get_len (replace_btn);
+ skip_btn = button_new (9, 28, B_SKIP_WORD, NORMAL_BUTTON, _("&Skip"), 0);
+ skip_len = button_get_len (skip_btn);
+ cancel_button = button_new (11, 28, B_CANCEL, NORMAL_BUTTON, _("&Cancel"), 0);
+ cancel_len = button_get_len (cancel_button);
+
+ max_btn_len = MAX (replace_len, skip_len);
+ max_btn_len = MAX (max_btn_len, cancel_len);
+
+ lang_label = g_strdup_printf ("%s: %s", _("Language"), aspell_get_lang ());
+ word_label = g_strdup_printf ("%s: %s", _("Misspelled"), word);
+ word_label_len = str_term_width1 (word_label) + 5;
+
+ sug_dlg_w += max_btn_len;
+ sug_dlg_w = MAX (sug_dlg_w, word_label_len) + 1;
+
+ sug_dlg = dlg_create (TRUE, ypos, xpos, sug_dlg_h, sug_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, NULL, NULL, "[ASpell]", _("Check word"));
+ g = GROUP (sug_dlg);
+
+ group_add_widget (g, label_new (1, 2, lang_label));
+ group_add_widget (g, label_new (3, 2, word_label));
+
+ group_add_widget (g, groupbox_new (4, 2, sug_dlg_h - 5, 25, _("Suggest")));
+
+ sug_list = listbox_new (5, 2, sug_dlg_h - 7, 24, FALSE, NULL);
+ for (i = 0; i < suggest->len; i++)
+ listbox_add_item (sug_list, LISTBOX_APPEND_AT_END, 0, g_ptr_array_index (suggest, i), NULL,
+ FALSE);
+ group_add_widget (g, sug_list);
+
+ group_add_widget (g, add_btn);
+ group_add_widget (g, replace_btn);
+ group_add_widget (g, skip_btn);
+ group_add_widget (g, cancel_button);
+
+ res = dlg_run (sug_dlg);
+ if (res == B_ENTER)
+ {
+ char *tmp = NULL;
+ listbox_get_current (sug_list, &curr, NULL);
+
+ if (curr != NULL)
+ tmp = g_strdup (curr);
+ *new_word = tmp;
+ }
+
+ widget_destroy (WIDGET (sug_dlg));
+ g_free (lang_label);
+ g_free (word_label);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show dialog to select language for spell check.
+ *
+ * @param languages Array of available languages
+ * @return name of chosen language
+ */
+
+const char *
+spell_dialog_lang_list_show (const GPtrArray * languages)
+{
+
+ int lang_dlg_h = 12; /* dialog height */
+ int lang_dlg_w = 30; /* dialog width */
+ const char *selected_lang = NULL;
+ unsigned int i;
+ int res;
+ Listbox *lang_list;
+
+ /* Create listbox */
+ lang_list = listbox_window_centered_new (-1, -1, lang_dlg_h, lang_dlg_w,
+ _("Select language"), "[ASpell]");
+
+ for (i = 0; i < languages->len; i++)
+ LISTBOX_APPEND_TEXT (lang_list, 0, g_ptr_array_index (languages, i), NULL, FALSE);
+
+ res = listbox_run (lang_list);
+ if (res >= 0)
+ selected_lang = g_ptr_array_index (languages, (unsigned int) res);
+
+ return selected_lang;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/spell.h b/src/editor/spell.h
new file mode 100644
index 0000000..005a2f5
--- /dev/null
+++ b/src/editor/spell.h
@@ -0,0 +1,36 @@
+#ifndef MC__EDIT_ASPELL_H
+#define MC__EDIT_ASPELL_H
+
+#include "lib/global.h" /* include <glib.h> */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void aspell_init (void);
+void aspell_clean (void);
+gboolean aspell_check (const char *word, const int word_size);
+unsigned int aspell_suggest (GPtrArray * suggest, const char *word, const int word_size);
+void aspell_array_clean (GPtrArray * array);
+unsigned int aspell_get_lang_list (GPtrArray * lang_list);
+const char *aspell_get_lang (void);
+gboolean aspell_set_lang (const char *lang);
+gboolean aspell_add_to_dict (const char *word, const int word_size);
+
+int edit_suggest_current_word (WEdit * edit);
+void edit_spellcheck_file (WEdit * edit);
+void edit_set_spell_lang (void);
+
+int spell_dialog_spell_suggest_show (WEdit * edit, const char *word, char **new_word,
+ const GPtrArray * suggest);
+const char *spell_dialog_lang_list_show (const GPtrArray * languages);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EDIT_ASPELL_H */
diff --git a/src/editor/syntax.c b/src/editor/syntax.c
new file mode 100644
index 0000000..f95ad2b
--- /dev/null
+++ b/src/editor/syntax.c
@@ -0,0 +1,1606 @@
+/*
+ Editor syntax highlighting.
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1998
+ Leonard den Ottolander <leonard den ottolander nl>, 2005, 2006
+ Egmont Koblinger <egmont@gmail.com>, 2010
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013, 2014, 2021
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: editor syntax highlighting
+ * \author Paul Sheer
+ * \date 1996, 1997
+ * \author Mikhail Pobolovets
+ * \date 2010
+ *
+ * Misspelled words are flushed from the syntax highlighting rules
+ * when they have been around longer than
+ * TRANSIENT_WORD_TIME_OUT seconds. At a cursor rate of 30
+ * chars per second and say 3 chars + a space per word, we can
+ * accumulate 450 words absolute max with a value of 60. This is
+ * below this limit of 1024 words in a context.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/search.h" /* search engine */
+#include "lib/skin.h"
+#include "lib/fileloc.h" /* EDIT_SYNTAX_DIR, EDIT_SYNTAX_FILE */
+#include "lib/strutil.h" /* utf string functions */
+#include "lib/util.h"
+#include "lib/widget.h" /* Listbox, message() */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+
+/*** global variables ****************************************************************************/
+
+gboolean auto_syntax = TRUE;
+
+/*** file scope macro definitions ****************************************************************/
+
+/* bytes */
+#define SYNTAX_MARKER_DENSITY 512
+
+#define RULE_ON_LEFT_BORDER 1
+#define RULE_ON_RIGHT_BORDER 2
+
+#define SYNTAX_TOKEN_STAR '\001'
+#define SYNTAX_TOKEN_PLUS '\002'
+#define SYNTAX_TOKEN_BRACKET '\003'
+#define SYNTAX_TOKEN_BRACE '\004'
+
+#define break_a { result = line; break; }
+#define check_a { if (*a == NULL) { result = line; break; } }
+#define check_not_a { if (*a != NULL) { result = line ;break; } }
+
+#define SYNTAX_KEYWORD(x) ((syntax_keyword_t *) (x))
+#define CONTEXT_RULE(x) ((context_rule_t *) (x))
+
+#define ARGS_LEN 1024
+
+#define MAX_ENTRY_LEN 40
+#define LIST_LINES 14
+#define N_DFLT_ENTRIES 2
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ GString *keyword;
+ char *whole_word_chars_left;
+ char *whole_word_chars_right;
+ gboolean line_start;
+ int color;
+} syntax_keyword_t;
+
+typedef struct
+{
+ GString *left;
+ unsigned char first_left;
+ GString *right;
+ unsigned char first_right;
+ gboolean line_start_left;
+ gboolean line_start_right;
+ gboolean between_delimiters;
+ char *whole_word_chars_left;
+ char *whole_word_chars_right;
+ char *keyword_first_chars;
+ gboolean spelling;
+ /* first word is word[1] */
+ GPtrArray *keyword;
+} context_rule_t;
+
+typedef struct
+{
+ off_t offset;
+ edit_syntax_rule_t rule;
+} syntax_marker_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static char *error_file_name = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+syntax_keyword_free (gpointer keyword)
+{
+ syntax_keyword_t *k = SYNTAX_KEYWORD (keyword);
+
+ g_string_free (k->keyword, TRUE);
+ g_free (k->whole_word_chars_left);
+ g_free (k->whole_word_chars_right);
+ g_free (k);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+context_rule_free (gpointer rule)
+{
+ context_rule_t *r = CONTEXT_RULE (rule);
+
+ g_string_free (r->left, TRUE);
+ g_string_free (r->right, TRUE);
+ g_free (r->whole_word_chars_left);
+ g_free (r->whole_word_chars_right);
+ g_free (r->keyword_first_chars);
+
+ if (r->keyword != NULL)
+ g_ptr_array_free (r->keyword, TRUE);
+
+ g_free (r);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gint
+mc_defines_destroy (gpointer key, gpointer value, gpointer data)
+{
+ (void) data;
+
+ g_free (key);
+ g_strfreev ((char **) value);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Completely destroys the defines tree */
+
+static void
+destroy_defines (GTree ** defines)
+{
+ g_tree_foreach (*defines, mc_defines_destroy, NULL);
+ g_tree_destroy (*defines);
+ *defines = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Wrapper for case insensitive mode */
+inline static int
+xx_tolower (const WEdit * edit, int c)
+{
+ return edit->is_case_insensitive ? tolower (c) : c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+subst_defines (GTree * defines, char **argv, char **argv_end)
+{
+ for (; *argv != NULL && argv < argv_end; argv++)
+ {
+ char **t;
+
+ t = g_tree_lookup (defines, *argv);
+ if (t != NULL)
+ {
+ int argc, count;
+ char **p;
+
+ /* Count argv array members */
+ argc = g_strv_length (argv + 1);
+
+ /* Count members of definition array */
+ count = g_strv_length (t);
+
+ p = argv + count + argc;
+ /* Buffer overflow or infinitive loop in define */
+ if (p >= argv_end)
+ break;
+
+ /* Move rest of argv after definition members */
+ while (argc >= 0)
+ *p-- = argv[argc-- + 1];
+
+ /* Copy definition members to argv */
+ for (p = argv; *t != NULL; *p++ = *t++)
+ ;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+compare_word_to_right (const WEdit * edit, off_t i, const GString * text,
+ const char *whole_left, const char *whole_right, gboolean line_start)
+{
+ const unsigned char *p, *q;
+ int c, d, j;
+
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i - 1));
+ if ((line_start && c != '\n') || (whole_left != NULL && strchr (whole_left, c) != NULL))
+ return -1;
+
+ for (p = (const unsigned char *) text->str, q = p + text->len; p < q; p++, i++)
+ {
+ switch (*p)
+ {
+ case SYNTAX_TOKEN_STAR:
+ if (++p > q)
+ return -1;
+ while (TRUE)
+ {
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i));
+ if (*p == '\0' && whole_right != NULL && strchr (whole_right, c) == NULL)
+ break;
+ if (c == *p)
+ break;
+ if (c == '\n')
+ return -1;
+ i++;
+ }
+ break;
+ case SYNTAX_TOKEN_PLUS:
+ if (++p > q)
+ return -1;
+ j = 0;
+ while (TRUE)
+ {
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i));
+ if (c == *p)
+ {
+ j = i;
+ if (p[0] == text->str[0] && p[1] == '\0') /* handle eg '+' and @+@ keywords properly */
+ break;
+ }
+ if (j != 0 && strchr ((const char *) p + 1, c) != NULL) /* c exists further down, so it will get matched later */
+ break;
+ if (whiteness (c) || (whole_right != NULL && strchr (whole_right, c) == NULL))
+ {
+ if (*p == '\0')
+ {
+ i--;
+ break;
+ }
+ if (j == 0)
+ return -1;
+ i = j;
+ break;
+ }
+ i++;
+ }
+ break;
+ case SYNTAX_TOKEN_BRACKET:
+ if (++p > q)
+ return -1;
+ c = -1;
+ while (TRUE)
+ {
+ d = c;
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i));
+ for (j = 0; p[j] != SYNTAX_TOKEN_BRACKET && p[j] != '\0'; j++)
+ if (c == p[j])
+ goto found_char2;
+ break;
+ found_char2:
+ i++;
+ }
+ i--;
+ while (*p != SYNTAX_TOKEN_BRACKET && p <= q)
+ p++;
+ if (p > q)
+ return -1;
+ if (p[1] == d)
+ i--;
+ break;
+ case SYNTAX_TOKEN_BRACE:
+ if (++p > q)
+ return -1;
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i));
+ for (; *p != SYNTAX_TOKEN_BRACE && *p != '\0'; p++)
+ if (c == *p)
+ goto found_char3;
+ return -1;
+ found_char3:
+ while (*p != SYNTAX_TOKEN_BRACE && p < q)
+ p++;
+ break;
+ default:
+ if (*p != xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i)))
+ return -1;
+ }
+ }
+ return (whole_right != NULL &&
+ strchr (whole_right,
+ xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i))) != NULL) ? -1 : i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+xx_strchr (const WEdit * edit, const unsigned char *s, int char_byte)
+{
+ while (*s >= '\005' && xx_tolower (edit, *s) != char_byte)
+ s++;
+
+ return (const char *) s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+apply_rules_going_right (WEdit * edit, off_t i)
+{
+ context_rule_t *r;
+ int c;
+ gboolean contextchanged = FALSE;
+ gboolean found_left = FALSE, found_right = FALSE;
+ gboolean keyword_foundleft = FALSE, keyword_foundright = FALSE;
+ gboolean is_end;
+ off_t end = 0;
+ edit_syntax_rule_t _rule = edit->rule;
+
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i));
+ if (c == 0)
+ return;
+
+ is_end = (edit->rule.end == i);
+
+ /* check to turn off a keyword */
+ if (_rule.keyword != 0)
+ {
+ if (edit_buffer_get_byte (&edit->buffer, i - 1) == '\n')
+ _rule.keyword = 0;
+ if (is_end)
+ {
+ _rule.keyword = 0;
+ keyword_foundleft = TRUE;
+ }
+ }
+
+ /* check to turn off a context */
+ if (_rule.context != 0 && _rule.keyword == 0)
+ {
+ off_t e;
+
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule.context));
+ if (r->first_right == c && (edit->rule.border & RULE_ON_RIGHT_BORDER) == 0
+ && r->right->len != 0 && (e =
+ compare_word_to_right (edit, i, r->right,
+ r->whole_word_chars_left,
+ r->whole_word_chars_right,
+ r->line_start_right)) > 0)
+ {
+ _rule.end = e;
+ found_right = TRUE;
+ _rule.border = RULE_ON_RIGHT_BORDER;
+ if (r->between_delimiters)
+ _rule.context = 0;
+ }
+ else if (is_end && (edit->rule.border & RULE_ON_RIGHT_BORDER) != 0)
+ {
+ /* always turn off a context at 4 */
+ found_left = TRUE;
+ _rule.border = 0;
+ if (!keyword_foundleft)
+ _rule.context = 0;
+ }
+ else if (is_end && (edit->rule.border & RULE_ON_LEFT_BORDER) != 0)
+ {
+ /* never turn off a context at 2 */
+ found_left = TRUE;
+ _rule.border = 0;
+ }
+ }
+
+ /* check to turn on a keyword */
+ if (_rule.keyword == 0)
+ {
+ const char *p;
+
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule.context));
+ p = r->keyword_first_chars;
+
+ if (p != NULL)
+ while (*(p = xx_strchr (edit, (const unsigned char *) p + 1, c)) != '\0')
+ {
+ syntax_keyword_t *k;
+ int count;
+ off_t e = -1;
+
+ count = p - r->keyword_first_chars;
+ k = SYNTAX_KEYWORD (g_ptr_array_index (r->keyword, count));
+ if (k->keyword != 0)
+ e = compare_word_to_right (edit, i, k->keyword, k->whole_word_chars_left,
+ k->whole_word_chars_right, k->line_start);
+ if (e > 0)
+ {
+ /* when both context and keyword terminate with a newline,
+ the context overflows to the next line and colorizes it incorrectly */
+ if (e > i + 1 && _rule._context != 0
+ && k->keyword->str[k->keyword->len - 1] == '\n')
+ {
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule._context));
+ if (r->right != NULL && r->right->len != 0
+ && r->right->str[r->right->len - 1] == '\n')
+ e--;
+ }
+
+ end = e;
+ _rule.end = e;
+ _rule.keyword = count;
+ keyword_foundright = TRUE;
+ break;
+ }
+ }
+ }
+
+ /* check to turn on a context */
+ if (_rule.context == 0)
+ {
+ if (!found_left && is_end)
+ {
+ if ((edit->rule.border & RULE_ON_RIGHT_BORDER) != 0)
+ {
+ _rule.border = 0;
+ _rule.context = 0;
+ contextchanged = TRUE;
+ _rule.keyword = 0;
+
+ }
+ else if ((edit->rule.border & RULE_ON_LEFT_BORDER) != 0)
+ {
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule._context));
+ _rule.border = 0;
+ if (r->between_delimiters)
+ {
+ _rule.context = _rule._context;
+ contextchanged = TRUE;
+ _rule.keyword = 0;
+
+ if (r->first_right == c)
+ {
+ off_t e = -1;
+
+ if (r->right->len != 0)
+ e = compare_word_to_right (edit, i, r->right, r->whole_word_chars_left,
+ r->whole_word_chars_right,
+ r->line_start_right);
+ if (e >= end)
+ {
+ _rule.end = e;
+ found_right = TRUE;
+ _rule.border = RULE_ON_RIGHT_BORDER;
+ _rule.context = 0;
+ }
+ }
+ }
+ }
+ }
+
+ if (!found_right)
+ {
+ size_t count;
+
+ for (count = 1; count < edit->rules->len; count++)
+ {
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, count));
+ if (r->first_left == c)
+ {
+ off_t e = -1;
+
+ if (r->left->len != 0)
+ e = compare_word_to_right (edit, i, r->left, r->whole_word_chars_left,
+ r->whole_word_chars_right, r->line_start_left);
+ if (e >= end && (_rule.keyword == 0 || keyword_foundright))
+ {
+ _rule.end = e;
+ _rule.border = RULE_ON_LEFT_BORDER;
+ _rule._context = count;
+ if (!r->between_delimiters && _rule.keyword == 0)
+ {
+ _rule.context = count;
+ contextchanged = TRUE;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /* check again to turn on a keyword if the context switched */
+ if (contextchanged && _rule.keyword == 0)
+ {
+ const char *p;
+
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule.context));
+ p = r->keyword_first_chars;
+
+ while (*(p = xx_strchr (edit, (const unsigned char *) p + 1, c)) != '\0')
+ {
+ syntax_keyword_t *k;
+ int count;
+ off_t e = -1;
+
+ count = p - r->keyword_first_chars;
+ k = SYNTAX_KEYWORD (g_ptr_array_index (r->keyword, count));
+
+ if (k->keyword->len != 0)
+ e = compare_word_to_right (edit, i, k->keyword, k->whole_word_chars_left,
+ k->whole_word_chars_right, k->line_start);
+ if (e > 0)
+ {
+ _rule.end = e;
+ _rule.keyword = count;
+ break;
+ }
+ }
+ }
+
+ edit->rule = _rule;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_get_rule (WEdit * edit, off_t byte_index)
+{
+ off_t i;
+
+ if (byte_index > edit->last_get_rule)
+ {
+ for (i = edit->last_get_rule + 1; i <= byte_index; i++)
+ {
+ off_t d = SYNTAX_MARKER_DENSITY;
+
+ apply_rules_going_right (edit, i);
+
+ if (edit->syntax_marker != NULL)
+ d += ((syntax_marker_t *) edit->syntax_marker->data)->offset;
+
+ if (i > d)
+ {
+ syntax_marker_t *s;
+
+ s = g_new (syntax_marker_t, 1);
+ s->offset = i;
+ s->rule = edit->rule;
+ edit->syntax_marker = g_slist_prepend (edit->syntax_marker, s);
+ }
+ }
+ }
+ else if (byte_index < edit->last_get_rule)
+ {
+ while (TRUE)
+ {
+ syntax_marker_t *s;
+
+ if (edit->syntax_marker == NULL)
+ {
+ memset (&edit->rule, 0, sizeof (edit->rule));
+ for (i = -1; i <= byte_index; i++)
+ apply_rules_going_right (edit, i);
+ break;
+ }
+
+ s = (syntax_marker_t *) edit->syntax_marker->data;
+
+ if (byte_index >= s->offset)
+ {
+ edit->rule = s->rule;
+ for (i = s->offset + 1; i <= byte_index; i++)
+ apply_rules_going_right (edit, i);
+ break;
+ }
+
+ g_free (s);
+ edit->syntax_marker = g_slist_delete_link (edit->syntax_marker, edit->syntax_marker);
+ }
+ }
+ edit->last_get_rule = byte_index;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+translate_rule_to_color (const WEdit * edit, const edit_syntax_rule_t * rule)
+{
+ syntax_keyword_t *k;
+ context_rule_t *r;
+
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, rule->context));
+ k = SYNTAX_KEYWORD (g_ptr_array_index (r->keyword, rule->keyword));
+
+ return k->color;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ Returns 0 on error/eof or a count of the number of bytes read
+ including the newline. Result must be free'd.
+ In case of an error, *line will not be modified.
+ */
+
+static size_t
+read_one_line (char **line, FILE * f)
+{
+ GString *p;
+ size_t r = 0;
+
+ /* not reallocate string too often */
+ p = g_string_sized_new (64);
+
+ while (TRUE)
+ {
+ int c;
+
+ c = fgetc (f);
+ if (c == EOF)
+ {
+ if (ferror (f))
+ {
+ if (errno == EINTR)
+ continue;
+ r = 0;
+ }
+ break;
+ }
+ r++;
+
+ /* handle all of \r\n, \r, \n correctly. */
+ if (c == '\n')
+ break;
+ if (c == '\r')
+ {
+ c = fgetc (f);
+ if (c == '\n')
+ r++;
+ else
+ ungetc (c, f);
+ break;
+ }
+
+ g_string_append_c (p, c);
+ }
+ if (r != 0)
+ *line = g_string_free (p, FALSE);
+ else
+ g_string_free (p, TRUE);
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+convert (char *s)
+{
+ char *r, *p;
+
+ p = r = s;
+ while (*s)
+ {
+ switch (*s)
+ {
+ case '\\':
+ s++;
+ switch (*s)
+ {
+ case ' ':
+ *p = ' ';
+ s--;
+ break;
+ case 'n':
+ *p = '\n';
+ break;
+ case 'r':
+ *p = '\r';
+ break;
+ case 't':
+ *p = '\t';
+ break;
+ case 's':
+ *p = ' ';
+ break;
+ case '*':
+ *p = '*';
+ break;
+ case '\\':
+ *p = '\\';
+ break;
+ case '[':
+ case ']':
+ *p = SYNTAX_TOKEN_BRACKET;
+ break;
+ case '{':
+ case '}':
+ *p = SYNTAX_TOKEN_BRACE;
+ break;
+ case 0:
+ *p = *s;
+ return r;
+ default:
+ *p = *s;
+ break;
+ }
+ break;
+ case '*':
+ *p = SYNTAX_TOKEN_STAR;
+ break;
+ case '+':
+ *p = SYNTAX_TOKEN_PLUS;
+ break;
+ default:
+ *p = *s;
+ break;
+ }
+ s++;
+ p++;
+ }
+ *p = '\0';
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+get_args (char *l, char **args, int args_size)
+{
+ int argc = 0;
+
+ while (argc < args_size)
+ {
+ char *p = l;
+
+ while (*p != '\0' && whiteness (*p))
+ p++;
+ if (*p == '\0')
+ break;
+ for (l = p + 1; *l != '\0' && !whiteness (*l); l++)
+ ;
+ if (*l != '\0')
+ *l++ = '\0';
+ args[argc++] = convert (p);
+ }
+ args[argc] = (char *) NULL;
+ return argc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+this_try_alloc_color_pair (const char *fg, const char *bg, const char *attrs)
+{
+ char f[80], b[80], a[80], *p;
+
+ if (bg != NULL && *bg == '\0')
+ bg = NULL;
+ if (fg != NULL && *fg == '\0')
+ fg = NULL;
+ if (attrs != NULL && *attrs == '\0')
+ attrs = NULL;
+
+ if ((fg == NULL) && (bg == NULL))
+ return EDITOR_NORMAL_COLOR;
+
+ if (fg != NULL)
+ {
+ g_strlcpy (f, fg, sizeof (f));
+ p = strchr (f, '/');
+ if (p != NULL)
+ *p = '\0';
+ fg = f;
+ }
+ if (bg != NULL)
+ {
+ g_strlcpy (b, bg, sizeof (b));
+ p = strchr (b, '/');
+ if (p != NULL)
+ *p = '\0';
+ bg = b;
+ }
+ if ((fg == NULL) || (bg == NULL))
+ {
+ /* get colors from skin */
+ char *editnormal;
+
+ editnormal = mc_skin_get ("editor", "_default_", "default;default");
+
+ if (fg == NULL)
+ {
+ g_strlcpy (f, editnormal, sizeof (f));
+ p = strchr (f, ';');
+ if (p != NULL)
+ *p = '\0';
+ if (f[0] == '\0')
+ g_strlcpy (f, "default", sizeof (f));
+ fg = f;
+ }
+ if (bg == NULL)
+ {
+ p = strchr (editnormal, ';');
+ if ((p != NULL) && (*(++p) != '\0'))
+ g_strlcpy (b, p, sizeof (b));
+ else
+ g_strlcpy (b, "default", sizeof (b));
+ bg = b;
+ }
+
+ g_free (editnormal);
+ }
+
+ if (attrs != NULL)
+ {
+ g_strlcpy (a, attrs, sizeof (a));
+ p = strchr (a, '/');
+ if (p != NULL)
+ *p = '\0';
+ /* get_args() mangles the + signs, unmangle 'em */
+ p = a;
+ while ((p = strchr (p, SYNTAX_TOKEN_PLUS)) != NULL)
+ *p++ = '+';
+ attrs = a;
+ }
+ return tty_try_alloc_color_pair (fg, bg, attrs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FILE *
+open_include_file (const char *filename)
+{
+ FILE *f;
+
+ g_free (error_file_name);
+ error_file_name = g_strdup (filename);
+ if (g_path_is_absolute (filename))
+ return fopen (filename, "r");
+
+ g_free (error_file_name);
+ error_file_name =
+ g_build_filename (mc_config_get_data_path (), EDIT_SYNTAX_DIR, filename, (char *) NULL);
+ f = fopen (error_file_name, "r");
+ if (f != NULL)
+ return f;
+
+ g_free (error_file_name);
+ error_file_name =
+ g_build_filename (mc_global.share_data_dir, EDIT_SYNTAX_DIR, filename, (char *) NULL);
+
+ return fopen (error_file_name, "r");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static void
+xx_lowerize_line (WEdit * edit, char *line, size_t len)
+{
+ if (edit->is_case_insensitive)
+ {
+ size_t i;
+
+ for (i = 0; i < len; ++i)
+ line[i] = tolower (line[i]);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns line number on error */
+
+static int
+edit_read_syntax_rules (WEdit * edit, FILE * f, char **args, int args_size)
+{
+ FILE *g = NULL;
+ char *fg, *bg, *attrs;
+ char last_fg[32] = "", last_bg[32] = "", last_attrs[64] = "";
+ char whole_right[512];
+ char whole_left[512];
+ char *l = NULL;
+ int save_line = 0, line = 0;
+ context_rule_t *c = NULL;
+ gboolean no_words = TRUE;
+ int result = 0;
+
+ args[0] = NULL;
+ edit->is_case_insensitive = FALSE;
+
+ strcpy (whole_left, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_01234567890");
+ strcpy (whole_right, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_01234567890");
+
+ edit->rules = g_ptr_array_new_with_free_func (context_rule_free);
+
+ if (edit->defines == NULL)
+ edit->defines = g_tree_new ((GCompareFunc) strcmp);
+
+ while (TRUE)
+ {
+ char **a;
+ size_t len;
+ int argc;
+
+ line++;
+ l = NULL;
+
+ len = read_one_line (&l, f);
+ if (len == 0)
+ {
+ if (g == NULL)
+ break;
+
+ fclose (f);
+ f = g;
+ g = NULL;
+ line = save_line + 1;
+ MC_PTR_FREE (error_file_name);
+ MC_PTR_FREE (l);
+ len = read_one_line (&l, f);
+ if (len == 0)
+ break;
+ }
+
+ xx_lowerize_line (edit, l, len);
+
+ argc = get_args (l, args, args_size);
+ a = args + 1;
+ if (args[0] == NULL)
+ {
+ /* do nothing */
+ }
+ else if (strcmp (args[0], "include") == 0)
+ {
+ if (g != NULL || argc != 2)
+ {
+ result = line;
+ break;
+ }
+ g = f;
+ f = open_include_file (args[1]);
+ if (f == NULL)
+ {
+ MC_PTR_FREE (error_file_name);
+ result = line;
+ break;
+ }
+ save_line = line;
+ line = 0;
+ }
+ else if (strcmp (args[0], "caseinsensitive") == 0)
+ {
+ edit->is_case_insensitive = TRUE;
+ }
+ else if (strcmp (args[0], "wholechars") == 0)
+ {
+ check_a;
+ if (strcmp (*a, "left") == 0)
+ {
+ a++;
+ g_strlcpy (whole_left, *a, sizeof (whole_left));
+ }
+ else if (strcmp (*a, "right") == 0)
+ {
+ a++;
+ g_strlcpy (whole_right, *a, sizeof (whole_right));
+ }
+ else
+ {
+ g_strlcpy (whole_left, *a, sizeof (whole_left));
+ g_strlcpy (whole_right, *a, sizeof (whole_right));
+ }
+ a++;
+ check_not_a;
+ }
+ else if (strcmp (args[0], "context") == 0)
+ {
+ syntax_keyword_t *k;
+
+ check_a;
+ if (edit->rules->len == 0)
+ {
+ /* first context is the default */
+ if (strcmp (*a, "default") != 0)
+ break_a;
+
+ a++;
+ c = g_new0 (context_rule_t, 1);
+ g_ptr_array_add (edit->rules, c);
+ c->left = g_string_new (" ");
+ c->right = g_string_new (" ");
+ }
+ else
+ {
+ /* Start new context. */
+ c = g_new0 (context_rule_t, 1);
+ g_ptr_array_add (edit->rules, c);
+ if (strcmp (*a, "exclusive") == 0)
+ {
+ a++;
+ c->between_delimiters = TRUE;
+ }
+ check_a;
+ if (strcmp (*a, "whole") == 0)
+ {
+ a++;
+ c->whole_word_chars_left = g_strdup (whole_left);
+ c->whole_word_chars_right = g_strdup (whole_right);
+ }
+ else if (strcmp (*a, "wholeleft") == 0)
+ {
+ a++;
+ c->whole_word_chars_left = g_strdup (whole_left);
+ }
+ else if (strcmp (*a, "wholeright") == 0)
+ {
+ a++;
+ c->whole_word_chars_right = g_strdup (whole_right);
+ }
+ check_a;
+ if (strcmp (*a, "linestart") == 0)
+ {
+ a++;
+ c->line_start_left = TRUE;
+ }
+ check_a;
+ c->left = g_string_new (*a++);
+ check_a;
+ if (strcmp (*a, "linestart") == 0)
+ {
+ a++;
+ c->line_start_right = TRUE;
+ }
+ check_a;
+ c->right = g_string_new (*a++);
+ c->first_left = c->left->str[0];
+ c->first_right = c->right->str[0];
+ }
+ c->keyword = g_ptr_array_new_with_free_func (syntax_keyword_free);
+ k = g_new0 (syntax_keyword_t, 1);
+ g_ptr_array_add (c->keyword, k);
+ no_words = FALSE;
+ subst_defines (edit->defines, a, &args[ARGS_LEN]);
+ fg = *a;
+ if (*a != NULL)
+ a++;
+ bg = *a;
+ if (*a != NULL)
+ a++;
+ attrs = *a;
+ if (*a != NULL)
+ a++;
+ g_strlcpy (last_fg, fg != NULL ? fg : "", sizeof (last_fg));
+ g_strlcpy (last_bg, bg != NULL ? bg : "", sizeof (last_bg));
+ g_strlcpy (last_attrs, attrs != NULL ? attrs : "", sizeof (last_attrs));
+ k->color = this_try_alloc_color_pair (fg, bg, attrs);
+ k->keyword = g_string_new (" ");
+ check_not_a;
+ }
+ else if (strcmp (args[0], "spellcheck") == 0)
+ {
+ if (c == NULL)
+ {
+ result = line;
+ break;
+ }
+ c->spelling = TRUE;
+ }
+ else if (strcmp (args[0], "keyword") == 0)
+ {
+ context_rule_t *last_rule;
+ syntax_keyword_t *k;
+
+ if (no_words)
+ break_a;
+ check_a;
+ last_rule = CONTEXT_RULE (g_ptr_array_index (edit->rules, edit->rules->len - 1));
+ k = g_new0 (syntax_keyword_t, 1);
+ g_ptr_array_add (last_rule->keyword, k);
+ if (strcmp (*a, "whole") == 0)
+ {
+ a++;
+ k->whole_word_chars_left = g_strdup (whole_left);
+ k->whole_word_chars_right = g_strdup (whole_right);
+ }
+ else if (strcmp (*a, "wholeleft") == 0)
+ {
+ a++;
+ k->whole_word_chars_left = g_strdup (whole_left);
+ }
+ else if (strcmp (*a, "wholeright") == 0)
+ {
+ a++;
+ k->whole_word_chars_right = g_strdup (whole_right);
+ }
+ check_a;
+ if (strcmp (*a, "linestart") == 0)
+ {
+ a++;
+ k->line_start = TRUE;
+ }
+ check_a;
+ if (strcmp (*a, "whole") == 0)
+ break_a;
+
+ k->keyword = g_string_new (*a++);
+ subst_defines (edit->defines, a, &args[ARGS_LEN]);
+ fg = *a;
+ if (*a != NULL)
+ a++;
+ bg = *a;
+ if (*a != NULL)
+ a++;
+ attrs = *a;
+ if (*a != NULL)
+ a++;
+ if (fg == NULL)
+ fg = last_fg;
+ if (bg == NULL)
+ bg = last_bg;
+ if (attrs == NULL)
+ attrs = last_attrs;
+ k->color = this_try_alloc_color_pair (fg, bg, attrs);
+ check_not_a;
+ }
+ else if (*(args[0]) == '#')
+ {
+ /* do nothing for comment */
+ }
+ else if (strcmp (args[0], "file") == 0)
+ {
+ break;
+ }
+ else if (strcmp (args[0], "define") == 0)
+ {
+ char *key = *a++;
+ char **argv;
+
+ if (argc < 3)
+ break_a;
+ argv = g_tree_lookup (edit->defines, key);
+ if (argv != NULL)
+ mc_defines_destroy (NULL, argv, NULL);
+ else
+ key = g_strdup (key);
+
+ argv = g_new (char *, argc - 1);
+ g_tree_insert (edit->defines, key, argv);
+ while (*a != NULL)
+ *argv++ = g_strdup (*a++);
+ *argv = NULL;
+ }
+ else
+ {
+ /* anything else is an error */
+ break_a;
+ }
+ MC_PTR_FREE (l);
+ }
+ MC_PTR_FREE (l);
+
+ if (edit->rules->len == 0)
+ {
+ g_ptr_array_free (edit->rules, TRUE);
+ edit->rules = NULL;
+ }
+
+ if (result == 0)
+ {
+ size_t i;
+ GString *first_chars;
+
+ if (edit->rules == NULL)
+ return line;
+
+ first_chars = g_string_sized_new (32);
+
+ /* collect first character of keywords */
+ for (i = 0; i < edit->rules->len; i++)
+ {
+ size_t j;
+
+ g_string_set_size (first_chars, 0);
+ c = CONTEXT_RULE (g_ptr_array_index (edit->rules, i));
+
+ g_string_append_c (first_chars, (char) 1);
+ for (j = 1; j < c->keyword->len; j++)
+ {
+ syntax_keyword_t *k;
+
+ k = SYNTAX_KEYWORD (g_ptr_array_index (c->keyword, j));
+ g_string_append_c (first_chars, k->keyword->str[0]);
+ }
+
+ c->keyword_first_chars = g_strndup (first_chars->str, first_chars->len);
+ }
+
+ g_string_free (first_chars, TRUE);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* returns -1 on file error, line number on error in file syntax */
+static int
+edit_read_syntax_file (WEdit * edit, GPtrArray * pnames, const char *syntax_file,
+ const char *editor_file, const char *first_line, const char *type)
+{
+ FILE *f, *g = NULL;
+ char *args[ARGS_LEN], *l = NULL;
+ long line = 0;
+ int result = 0;
+ gboolean found = FALSE;
+
+ f = fopen (syntax_file, "r");
+ if (f == NULL)
+ {
+ char *global_syntax_file;
+
+ global_syntax_file =
+ g_build_filename (mc_global.share_data_dir, EDIT_SYNTAX_FILE, (char *) NULL);
+ f = fopen (global_syntax_file, "r");
+ g_free (global_syntax_file);
+ if (f == NULL)
+ return -1;
+ }
+
+ args[0] = NULL;
+ while (TRUE)
+ {
+ line++;
+ MC_PTR_FREE (l);
+ if (read_one_line (&l, f) == 0)
+ break;
+ (void) get_args (l, args, ARGS_LEN - 1); /* Final NULL */
+ if (args[0] == NULL)
+ continue;
+
+ /* Looking for 'include ...' lines before first 'file ...' ones */
+ if (!found && strcmp (args[0], "include") == 0)
+ {
+ if (args[1] == NULL || (g = open_include_file (args[1])) == NULL)
+ {
+ result = line;
+ break;
+ }
+ goto found_type;
+ }
+
+ /* looking for 'file ...' lines only */
+ if (strcmp (args[0], "file") != 0)
+ continue;
+
+ found = TRUE;
+
+ /* must have two args or report error */
+ if (args[1] == NULL || args[2] == NULL)
+ {
+ result = line;
+ break;
+ }
+
+ if (pnames != NULL)
+ {
+ /* 1: just collecting a list of names of rule sets */
+ g_ptr_array_add (pnames, g_strdup (args[2]));
+ }
+ else if (type != NULL)
+ {
+ /* 2: rule set was explicitly specified by the caller */
+ if (strcmp (type, args[2]) == 0)
+ goto found_type;
+ }
+ else if (editor_file != NULL && edit != NULL)
+ {
+ /* 3: auto-detect rule set from regular expressions */
+ gboolean q;
+
+ q = mc_search (args[1], DEFAULT_CHARSET, editor_file, MC_SEARCH_T_REGEX);
+ /* does filename match arg 1 ? */
+ if (!q && args[3] != NULL)
+ {
+ /* does first line match arg 3 ? */
+ q = mc_search (args[3], DEFAULT_CHARSET, first_line, MC_SEARCH_T_REGEX);
+ }
+ if (q)
+ {
+ int line_error;
+ char *syntax_type;
+
+ found_type:
+ syntax_type = args[2];
+ line_error = edit_read_syntax_rules (edit, g ? g : f, args, ARGS_LEN - 1);
+ if (line_error != 0)
+ {
+ if (error_file_name == NULL) /* an included file */
+ result = line + line_error;
+ else
+ result = line_error;
+ }
+ else
+ {
+ g_free (edit->syntax_type);
+ edit->syntax_type = g_strdup (syntax_type);
+ /* if there are no rules then turn off syntax highlighting for speed */
+ if (g == NULL && edit->rules->len == 1)
+ {
+ context_rule_t *r0;
+
+ r0 = CONTEXT_RULE (g_ptr_array_index (edit->rules, 0));
+ if (r0->keyword->len == 1 && !r0->spelling)
+ {
+ edit_free_syntax_rules (edit);
+ break;
+ }
+ }
+ }
+
+ if (g == NULL)
+ break;
+
+ fclose (g);
+ g = NULL;
+ }
+ }
+ }
+ g_free (l);
+ fclose (f);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+get_first_editor_line (WEdit * edit)
+{
+ static char s[256];
+
+ s[0] = '\0';
+
+ if (edit != NULL)
+ {
+ size_t i;
+
+ for (i = 0; i < sizeof (s) - 1; i++)
+ {
+ s[i] = edit_buffer_get_byte (&edit->buffer, i);
+ if (s[i] == '\n')
+ {
+ s[i] = '\0';
+ break;
+ }
+ }
+
+ s[sizeof (s) - 1] = '\0';
+ }
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+pstrcmp (const void *p1, const void *p2)
+{
+ return strcmp (*(char *const *) p1, *(char *const *) p2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+exec_edit_syntax_dialog (const GPtrArray * names, const char *current_syntax)
+{
+ size_t i;
+ Listbox *syntaxlist;
+
+ syntaxlist = listbox_window_new (LIST_LINES, MAX_ENTRY_LEN,
+ _("Choose syntax highlighting"), NULL);
+ LISTBOX_APPEND_TEXT (syntaxlist, 'A', _("< Auto >"), NULL, FALSE);
+ LISTBOX_APPEND_TEXT (syntaxlist, 'R', _("< Reload Current Syntax >"), NULL, FALSE);
+
+ for (i = 0; i < names->len; i++)
+ {
+ const char *name;
+
+ name = g_ptr_array_index (names, i);
+ LISTBOX_APPEND_TEXT (syntaxlist, 0, name, NULL, FALSE);
+ if (current_syntax != NULL && strcmp (name, current_syntax) == 0)
+ listbox_set_current (syntaxlist->list, i + N_DFLT_ENTRIES);
+ }
+
+ return listbox_run (syntaxlist);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+edit_get_syntax_color (WEdit * edit, off_t byte_index)
+{
+ if (!tty_use_colors ())
+ return 0;
+
+ if (edit_options.syntax_highlighting && edit->rules != NULL && byte_index < edit->buffer.size)
+ {
+ edit_get_rule (edit, byte_index);
+ return translate_rule_to_color (edit, &edit->rule);
+ }
+
+ return EDITOR_NORMAL_COLOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_free_syntax_rules (WEdit * edit)
+{
+ if (edit == NULL)
+ return;
+
+ if (edit->defines != NULL)
+ destroy_defines (&edit->defines);
+
+ if (edit->rules == NULL)
+ return;
+
+ edit_get_rule (edit, -1);
+ MC_PTR_FREE (edit->syntax_type);
+
+ g_ptr_array_free (edit->rules, TRUE);
+ edit->rules = NULL;
+ g_clear_slist (&edit->syntax_marker, g_free);
+ tty_color_free_all_tmp ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load rules into edit struct. Either edit or *pnames must be NULL. If
+ * edit is NULL, a list of types will be stored into names. If type is
+ * NULL, then the type will be selected according to the filename.
+ * type must be edit->syntax_type or NULL
+ */
+void
+edit_load_syntax (WEdit * edit, GPtrArray * pnames, const char *type)
+{
+ int r;
+ char *f = NULL;
+
+ if (auto_syntax)
+ type = NULL;
+
+ if (edit != NULL)
+ {
+ char *saved_type;
+
+ saved_type = g_strdup (type); /* save edit->syntax_type */
+ edit_free_syntax_rules (edit);
+ edit->syntax_type = saved_type; /* restore edit->syntax_type */
+ }
+
+ if (!tty_use_colors ())
+ return;
+
+ if (!edit_options.syntax_highlighting && (pnames == NULL || pnames->len == 0))
+ return;
+
+ if (edit != NULL && edit->filename_vpath == NULL)
+ return;
+
+ f = mc_config_get_full_path (EDIT_SYNTAX_FILE);
+ if (edit != NULL)
+ r = edit_read_syntax_file (edit, pnames, f, vfs_path_as_str (edit->filename_vpath),
+ get_first_editor_line (edit),
+ auto_syntax ? NULL : edit->syntax_type);
+ else
+ r = edit_read_syntax_file (NULL, pnames, f, NULL, "", NULL);
+ if (r == -1)
+ {
+ edit_free_syntax_rules (edit);
+ message (D_ERROR, _("Load syntax file"),
+ _("Cannot open file %s\n%s"), f, unix_error_string (errno));
+ }
+ else if (r != 0)
+ {
+ edit_free_syntax_rules (edit);
+ message (D_ERROR, _("Load syntax file"),
+ _("Error in file %s on line %d"), error_file_name != NULL ? error_file_name : f,
+ r);
+ MC_PTR_FREE (error_file_name);
+ }
+
+ g_free (f);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+edit_get_syntax_type (const WEdit * edit)
+{
+ return edit->syntax_type;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_syntax_dialog (WEdit * edit)
+{
+ GPtrArray *names;
+ int syntax;
+
+ names = g_ptr_array_new_with_free_func (g_free);
+
+ /* We fill the list of syntax files every time the editor is invoked.
+ Instead we could save the list to a file and update it once the syntax
+ file gets updated (either by testing or by explicit user command). */
+ edit_load_syntax (NULL, names, NULL);
+ g_ptr_array_sort (names, pstrcmp);
+
+ syntax = exec_edit_syntax_dialog (names, edit->syntax_type);
+ if (syntax >= 0)
+ {
+ gboolean force_reload = FALSE;
+ char *current_syntax;
+ gboolean old_auto_syntax;
+
+ current_syntax = g_strdup (edit->syntax_type);
+ old_auto_syntax = auto_syntax;
+
+ switch (syntax)
+ {
+ case 0: /* auto syntax */
+ auto_syntax = TRUE;
+ break;
+ case 1: /* reload current syntax */
+ force_reload = TRUE;
+ break;
+ default:
+ auto_syntax = FALSE;
+ g_free (edit->syntax_type);
+ edit->syntax_type = g_strdup (g_ptr_array_index (names, syntax - N_DFLT_ENTRIES));
+ }
+
+ /* Load or unload syntax rules if the option has changed */
+ if (force_reload || (auto_syntax && !old_auto_syntax) || old_auto_syntax ||
+ (current_syntax != NULL && edit->syntax_type != NULL &&
+ strcmp (current_syntax, edit->syntax_type) != 0))
+ edit_load_syntax (edit, NULL, edit->syntax_type);
+
+ g_free (current_syntax);
+ }
+
+ g_ptr_array_free (names, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */