diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 17:44:12 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 17:44:12 +0000 |
commit | 8ccb487c21368a7fdc8c7c72315325bf0aa06147 (patch) | |
tree | b2056fae01d325924508a41731edfbd4c3cddd23 /src/editor | |
parent | Initial commit. (diff) | |
download | mc-upstream.tar.xz mc-upstream.zip |
Adding upstream version 3:4.8.29.upstream/3%4.8.29upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/editor')
-rw-r--r-- | src/editor/Makefile.am | 33 | ||||
-rw-r--r-- | src/editor/Makefile.in | 800 | ||||
-rw-r--r-- | src/editor/bookmark.c | 347 | ||||
-rw-r--r-- | src/editor/edit-impl.h | 278 | ||||
-rw-r--r-- | src/editor/edit.c | 4057 | ||||
-rw-r--r-- | src/editor/edit.h | 78 | ||||
-rw-r--r-- | src/editor/editbuffer.c | 906 | ||||
-rw-r--r-- | src/editor/editbuffer.h | 117 | ||||
-rw-r--r-- | src/editor/editcmd.c | 2113 | ||||
-rw-r--r-- | src/editor/editcomplete.c | 475 | ||||
-rw-r--r-- | src/editor/editcomplete.h | 21 | ||||
-rw-r--r-- | src/editor/editdraw.c | 1128 | ||||
-rw-r--r-- | src/editor/editmacros.c | 433 | ||||
-rw-r--r-- | src/editor/editmacros.h | 24 | ||||
-rw-r--r-- | src/editor/editmenu.c | 346 | ||||
-rw-r--r-- | src/editor/editoptions.c | 240 | ||||
-rw-r--r-- | src/editor/editsearch.c | 1034 | ||||
-rw-r--r-- | src/editor/editsearch.h | 36 | ||||
-rw-r--r-- | src/editor/editwidget.c | 1548 | ||||
-rw-r--r-- | src/editor/editwidget.h | 173 | ||||
-rw-r--r-- | src/editor/etags.c | 467 | ||||
-rw-r--r-- | src/editor/etags.h | 26 | ||||
-rw-r--r-- | src/editor/format.c | 539 | ||||
-rw-r--r-- | src/editor/spell.c | 852 | ||||
-rw-r--r-- | src/editor/spell.h | 36 | ||||
-rw-r--r-- | src/editor/syntax.c | 1604 |
26 files changed, 17711 insertions, 0 deletions
diff --git a/src/editor/Makefile.am b/src/editor/Makefile.am new file mode 100644 index 0000000..5741959 --- /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) $(PCRE_CPPFLAGS) diff --git a/src/editor/Makefile.in b/src/editor/Makefile.in new file mode 100644 index 0000000..df912b8 --- /dev/null +++ b/src/editor/Makefile.in @@ -0,0 +1,800 @@ +# 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/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_CPPFLAGS = @PCRE_CPPFLAGS@ +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) $(PCRE_CPPFLAGS) +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..59c53d2 --- /dev/null +++ b/src/editor/bookmark.c @@ -0,0 +1,347 @@ +/* + Editor book mark handling + + Copyright (C) 2001-2022 + 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 <errno.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 ****************************************************************/ + +/*** 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..e1eba6e --- /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 "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 option_line_state_width; + +extern int option_max_undo; +extern gboolean option_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 *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..30fe211 --- /dev/null +++ b/src/editor/edit.c @@ -0,0 +1,4057 @@ +/* + Editor low level data handling and cursor fundamentals. + + Copyright (C) 1996-2022 + 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 <errno.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/setup.h" /* option_tab_spacing */ +#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 ****************************************************************************/ + +int option_word_wrap_line_length = DEFAULT_WRAP_LINE_LENGTH; +gboolean option_typewriter_wrap = FALSE; +gboolean option_auto_para_formatting = FALSE; +gboolean option_fill_tabs_with_spaces = FALSE; +gboolean option_return_does_auto_indent = TRUE; +gboolean option_backspace_through_tabs = FALSE; +gboolean option_fake_half_tabs = TRUE; +int option_save_mode = EDIT_QUICK_SAVE; +gboolean option_save_position = TRUE; +int option_max_undo = 32768; +gboolean option_persistent_selections = TRUE; +gboolean option_cursor_beyond_eol = FALSE; +gboolean option_line_state = FALSE; +int option_line_state_width = 0; +gboolean option_cursor_after_inserted_block = FALSE; +gboolean option_state_full_filename = FALSE; + +gboolean enable_show_tabs_tws = TRUE; +gboolean option_check_nl_at_eof = FALSE; +gboolean option_group_undo = FALSE; +gboolean show_right_margin = FALSE; + +char *option_backup_ext = NULL; +char *option_filesize_threshold = NULL; + +unsigned int edit_stack_iterator = 0; +edit_stack_type edit_history_moveto[MAX_HISTORY_MOVETO]; +/* magic sequense 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 ****************************************************************/ + +/*** 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 option_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) +{ + size_t i, l; + + if (filename_vpath == NULL) + return -1; + + l = strlen (vfs_path_as_str (filename_vpath)); + for (i = 0; i < G_N_ELEMENTS (all_filters); i++) + { + size_t e; + + e = strlen (all_filters[i].extension); + if (l > e) + if (!strcmp (all_filters[i].extension, vfs_path_as_str (filename_vpath) + l - e)) + 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 = 0; + + while ((c = fgetc (f)) >= 0) + { + edit_insert (edit, c); + i++; + } + 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 (option_filesize_threshold, &err); + if (err) + threshold = option_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 aply 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 option_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 (option_chars_move_whole_word, c); + if (!q) + return 0xFFFFFFFFUL; + do + { + for (x = 1, p = option_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 (option_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 + && option_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 (option_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 option_group_undo is not set, + * and make single UNDO operation + */ + if (!option_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 = option_tab_spacing * 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 (option_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 (option_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 (option_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 (!option_typewriter_wrap) + return; + edit_update_curs_col (edit); + if (edit->curs_col < option_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 seach 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 (option_fill_tabs_with_spaces) + insert_spaces_tab (edit, option_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); + + if (option_fake_half_tabs) + del_tab_width = HALF_TAB_SIZE; + else + del_tab_width = option_tab_spacing; + + 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 (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; + char *p, *writename; + const vfs_path_element_t *path_element; + + i = edit_find_filter (filename_vpath); + if (i < 0) + return NULL; + + path_element = vfs_path_get_by_index (write_name_vpath, -1); + writename = name_quote (path_element->path, FALSE); + p = g_strdup_printf (all_filters[i].write, writename); + g_free (writename); + 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 (!option_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 (!option_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 (!option_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 (!option_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; + + option_auto_syntax = TRUE; /* Resetting to auto on every invokation */ + option_line_state_width = option_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) && option_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 (option_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 betwen 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; + long *t; + + /* first enlarge the stack if necessary */ + if (sp > edit->undo_stack_size - 10) + { /* say */ + if (option_max_undo < 256) + option_max_undo = 256; + if (edit->undo_stack_size < (unsigned long) option_max_undo) + { + t = g_realloc (edit->undo_stack, (edit->undo_stack_size * 2 + 10) * sizeof (long)); + if (t) + { + 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; + long *t; + /* first enlarge the stack if necessary */ + if (sp > edit->redo_stack_size - 10) + { /* say */ + if (option_max_undo < 256) + option_max_undo = 256; + if (edit->redo_stack_size < (unsigned long) option_max_undo) + { + t = g_realloc (edit->redo_stack, (edit->redo_stack_size * 2 + 10) * sizeof (long)); + if (t) + { + 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 (option_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 (option_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 (!option_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 (option_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 (option_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 (!option_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 (!option_persistent_selections && edit->mark1 != edit->mark2) + edit_block_delete_cmd (edit); + else if (option_cursor_beyond_eol && edit->over_col > 0) + edit->over_col--; + else if (option_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 (option_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 (!option_persistent_selections && edit->mark1 != edit->mark2) + edit_block_delete_cmd (edit); + else + { + if (option_cursor_beyond_eol && edit->over_col > 0) + edit_insert_over (edit); + + if (option_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 (option_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 (option_auto_para_formatting) + { + edit_double_newline (edit); + if (option_return_does_auto_indent && !bracketed_pasting_in_progress) + edit_auto_indent (edit); + format_paragraph (edit, FALSE); + } + else + { + edit_insert (edit, '\n'); + if (option_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 (option_fake_half_tabs && is_in_indent (&edit->buffer) && right_of_four_spaces (edit)) + { + if (option_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 (option_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 && !option_persistent_selections) + { + if (edit->mark2 < 0) + edit_mark_cmd (edit, FALSE); + edit_move_block_to_right (edit); + } + else + { + if (option_cursor_beyond_eol) + edit_insert_over (edit); + edit_tab_cmd (edit); + if (option_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 (option_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 (!option_persistent_selections && edit->mark1 != edit->mark2) + edit_block_delete_cmd (edit); + if (option_cursor_beyond_eol && edit->over_col > 0) + edit_insert_over (edit); + edit_paste_from_X_buf_cmd (edit); + if (!option_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 && !option_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 (option_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..6c519e9 --- /dev/null +++ b/src/editor/edit.h @@ -0,0 +1,78 @@ +/* + 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 + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/* Editor widget */ +struct WEdit; +typedef struct WEdit WEdit; + +/*** global variables defined in .c file *********************************************************/ + +extern int option_word_wrap_line_length; +extern gboolean option_typewriter_wrap; +extern gboolean option_auto_para_formatting; +extern gboolean option_fill_tabs_with_spaces; +extern gboolean option_return_does_auto_indent; +extern gboolean option_backspace_through_tabs; +extern gboolean option_fake_half_tabs; +extern gboolean option_persistent_selections; +extern gboolean option_drop_selection_on_copy; +extern gboolean option_cursor_beyond_eol; +extern gboolean option_cursor_after_inserted_block; +extern gboolean option_state_full_filename; +extern gboolean option_line_state; +extern int option_save_mode; +extern gboolean option_save_position; +extern gboolean option_syntax_highlighting; +extern gboolean option_group_undo; +extern char *option_backup_ext; +extern char *option_filesize_threshold; +extern char *option_stop_format_chars; + +extern gboolean edit_confirm_save; + +extern gboolean visible_tabs; +extern gboolean visible_tws; + +extern gboolean simple_statusbar; +extern gboolean option_check_nl_at_eof; +extern gboolean show_right_margin; + +/*** 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..698fcb0 --- /dev/null +++ b/src/editor/editbuffer.c @@ -0,0 +1,906 @@ +/* + Editor text keep buffer. + + Copyright (C) 2013-2022 + 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 ****************************************************************/ + +/*** 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_sized_new (32); + buf->b2 = g_ptr_array_sized_new (32); + + 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_foreach (buf->b1, (GFunc) g_free, NULL); + g_ptr_array_free (buf->b1, TRUE); + } + + if (buf->b2 != NULL) + { + g_ptr_array_foreach (buf->b2, (GFunc) g_free, 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); + g_free (b); + } + + 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); + g_free (b); + } + + 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..5a0c86a --- /dev/null +++ b/src/editor/editcmd.c @@ -0,0 +1,2113 @@ +/* + Editor high level editing commands + + Copyright (C) 1996-2022 + 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 <errno.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() */ +#include "src/setup.h" /* option_tab_spacing */ +#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; + +/* queries on a save */ +gboolean edit_confirm_save = TRUE; + +/* whether we need to drop selection on copy to buffer */ +gboolean option_drop_selection_on_copy = TRUE; + +/*** file scope macro definitions ****************************************************************/ + +#define space_width 1 + +#define TEMP_BUF_LEN 1024 + +/*** file scope type declarations ****************************************************************/ + +/*** 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 = option_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 vfs_path_element_t *path_element; + + mc_close (fd); + + path_element = vfs_path_get_by_index (savename_vpath, -1); + file = (FILE *) fopen (path_element->path, "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"), path_element->path); + 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 (option_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, option_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 !(option_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) > option_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 (option_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 = (WEdit *) data; + + if (option_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_stop (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) +{ + option_syntax_highlighting = !option_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) +{ + show_right_margin = !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) +{ + option_line_state = !option_line_state; + option_line_state_width = option_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 (option_backup_ext != NULL); + + { + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_RADIO (3, str, &option_save_mode, &edit_save_mode_radio_id), + QUICK_INPUT (option_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"), &option_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 (option_backup_ext); + option_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_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 = (WEdit *) (g->current->data); + else + { + 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 (option_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 (option_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 (option_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 (option_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..dc4d40a --- /dev/null +++ b/src/editor/editcomplete.c @@ -0,0 +1,475 @@ +/* + Editor word completion engine + + Copyright (C) 2021-2022 + 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 ****************************************************************/ + +/*** 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_convert_to_display (temp->str); + 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 = (WEdit *) 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); + + 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) + option_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 + option_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..b4ec402 --- /dev/null +++ b/src/editor/editdraw.c @@ -0,0 +1,1128 @@ +/* + Editor text drawing. + + Copyright (C) 1996-2022 + 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 <errno.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 "src/setup.h" /* edit_tab_spacing */ + +#include "edit-impl.h" +#include "editwidget.h" + +/*** global variables ****************************************************************************/ + +/* Toggles statusbar draw style */ +gboolean simple_statusbar = FALSE; + +gboolean visible_tws = TRUE; +gboolean visible_tabs = TRUE; + +/*** 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; + +/*** 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 (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 (!option_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 (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 (!option_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 + option_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 = option_word_wrap_line_length + edit->start_col; + + if (len > 0 && w->rect.y + y >= 0) + { + if (!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 (option_line_state) + { + tty_setcolor (LINE_STATE_COLOR); + + for (i = 0; i < LINE_STATE_WIDTH; i++) + { + edit_move (x1 + i - option_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 (show_right_margin) + { + if (i > option_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 + option_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 (option_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 (tty_use_colors () && visible_tws) + 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 (tty_use_colors () && (visible_tabs || (visible_tws && q >= tws)) + && enable_show_tabs_tws) + { + 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 (tty_use_colors () && visible_tws && q >= tws + && enable_show_tabs_tws) + { + 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 (tty_use_colors () && visible_tws && q >= tws && enable_show_tabs_tws) + { + 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 + option_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 + option_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..d95f84b --- /dev/null +++ b/src/editor/editmacros.c @@ -0,0 +1,433 @@ +/* + Editor macros engine + + Copyright (C) 2001-2022 + 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" /* lookup_key*() */ +#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 ****************************************************************/ + +/*** 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 = lookup_key_by_code (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 = lookup_key_by_code (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; + macros_t macro; + + values = mc_config_get_string_list (macros_config, section_name, *profile_keys, NULL); + hotkey = lookup_key (*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) + { + macro.hotkey = hotkey; + macro.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..75e965a --- /dev/null +++ b/src/editor/editmenu.c @@ -0,0 +1,346 @@ +/* + Editor menu definitions and initialisation + + Copyright (C) 1996-2022 + 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 <errno.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 ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static GList * +create_file_menu (void) +{ + GList *entries = NULL; + + entries = g_list_prepend (entries, menu_entry_create (_("&Open file..."), CK_EditFile)); + entries = g_list_prepend (entries, menu_entry_create (_("&New"), CK_EditNew)); + entries = g_list_prepend (entries, menu_entry_create (_("&Close"), CK_Close)); + entries = g_list_prepend (entries, menu_entry_create (_("&History..."), CK_History)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Save"), CK_Save)); + entries = g_list_prepend (entries, menu_entry_create (_("Save &as..."), CK_SaveAs)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Insert file..."), CK_InsertFile)); + entries = g_list_prepend (entries, menu_entry_create (_("Cop&y to file..."), CK_BlockSave)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&User menu..."), CK_UserMenu)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("A&bout..."), CK_About)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Quit"), CK_Quit)); + + return g_list_reverse (entries); +} + +/* --------------------------------------------------------------------------------------------- */ + +static GList * +create_edit_menu (void) +{ + GList *entries = NULL; + + entries = g_list_prepend (entries, menu_entry_create (_("&Undo"), CK_Undo)); + entries = g_list_prepend (entries, menu_entry_create (_("&Redo"), CK_Redo)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = + g_list_prepend (entries, menu_entry_create (_("&Toggle ins/overw"), CK_InsertOverwrite)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("To&ggle mark"), CK_Mark)); + entries = g_list_prepend (entries, menu_entry_create (_("&Mark columns"), CK_MarkColumn)); + entries = g_list_prepend (entries, menu_entry_create (_("Mark &all"), CK_MarkAll)); + entries = g_list_prepend (entries, menu_entry_create (_("Unmar&k"), CK_Unmark)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("Cop&y"), CK_Copy)); + entries = g_list_prepend (entries, menu_entry_create (_("Mo&ve"), CK_Move)); + entries = g_list_prepend (entries, menu_entry_create (_("&Delete"), CK_Remove)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("Co&py to clipfile"), CK_Store)); + entries = g_list_prepend (entries, menu_entry_create (_("&Cut to clipfile"), CK_Cut)); + entries = g_list_prepend (entries, menu_entry_create (_("Pa&ste from clipfile"), CK_Paste)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Beginning"), CK_Top)); + entries = g_list_prepend (entries, menu_entry_create (_("&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_create (_("&Search..."), CK_Search)); + entries = g_list_prepend (entries, menu_entry_create (_("Search &again"), CK_SearchContinue)); + entries = g_list_prepend (entries, menu_entry_create (_("&Replace..."), CK_Replace)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Toggle bookmark"), CK_Bookmark)); + entries = g_list_prepend (entries, menu_entry_create (_("&Next bookmark"), CK_BookmarkNext)); + entries = g_list_prepend (entries, menu_entry_create (_("&Prev bookmark"), CK_BookmarkPrev)); + entries = g_list_prepend (entries, menu_entry_create (_("&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_create (_("&Go to line..."), CK_Goto)); + entries = g_list_prepend (entries, menu_entry_create (_("&Toggle line state"), CK_ShowNumbers)); + entries = + g_list_prepend (entries, menu_entry_create (_("Go to matching &bracket"), CK_MatchBracket)); + entries = + g_list_prepend (entries, + menu_entry_create (_("Toggle s&yntax highlighting"), CK_SyntaxOnOff)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Find declaration"), CK_Find)); + entries = + g_list_prepend (entries, menu_entry_create (_("Back from &declaration"), CK_FilePrev)); + entries = + g_list_prepend (entries, menu_entry_create (_("For&ward to declaration"), CK_FileNext)); +#ifdef HAVE_CHARSET + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("Encod&ing..."), CK_SelectCodepage)); +#endif + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Refresh screen"), CK_Refresh)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = + g_list_prepend (entries, + menu_entry_create (_("&Start/Stop record macro"), CK_MacroStartStopRecord)); + entries = g_list_prepend (entries, menu_entry_create (_("Delete macr&o..."), CK_MacroDelete)); + entries = + g_list_prepend (entries, + menu_entry_create (_("Record/Repeat &actions"), CK_RepeatStartStopRecord)); + entries = g_list_prepend (entries, menu_separator_create ()); +#ifdef HAVE_ASPELL + if (strcmp (spell_language, "NONE") != 0) + { + entries = g_list_prepend (entries, menu_entry_create (_("S&pell check"), CK_SpellCheck)); + entries = + g_list_prepend (entries, + menu_entry_create (_("C&heck word"), CK_SpellCheckCurrentWord)); + entries = + g_list_prepend (entries, + menu_entry_create (_("Change spelling &language..."), + CK_SpellCheckSelectLang)); + entries = g_list_prepend (entries, menu_separator_create ()); + } +#endif /* HAVE_ASPELL */ + entries = g_list_prepend (entries, menu_entry_create (_("&Mail..."), CK_EditMail)); + + + return g_list_reverse (entries); +} + +/* --------------------------------------------------------------------------------------------- */ + +static GList * +create_format_menu (void) +{ + GList *entries = NULL; + + entries = + g_list_prepend (entries, menu_entry_create (_("Insert &literal..."), CK_InsertLiteral)); + entries = g_list_prepend (entries, menu_entry_create (_("Insert &date/time"), CK_Date)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = + g_list_prepend (entries, menu_entry_create (_("&Format paragraph"), CK_ParagraphFormat)); + entries = g_list_prepend (entries, menu_entry_create (_("&Sort..."), CK_Sort)); + entries = + g_list_prepend (entries, menu_entry_create (_("&Paste output of..."), CK_ExternalCommand)); + entries = + g_list_prepend (entries, menu_entry_create (_("&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_create (_("&Move"), CK_WindowMove)); + entries = g_list_prepend (entries, menu_entry_create (_("&Resize"), CK_WindowResize)); + entries = + g_list_prepend (entries, menu_entry_create (_("&Toggle fullscreen"), CK_WindowFullscreen)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Next"), CK_WindowNext)); + entries = g_list_prepend (entries, menu_entry_create (_("&Previous"), CK_WindowPrev)); + entries = g_list_prepend (entries, menu_entry_create (_("&List..."), CK_WindowList)); + + return g_list_reverse (entries); +} + +/* --------------------------------------------------------------------------------------------- */ + +static GList * +create_options_menu (void) +{ + GList *entries = NULL; + + entries = g_list_prepend (entries, menu_entry_create (_("&General..."), CK_Options)); + entries = g_list_prepend (entries, menu_entry_create (_("Save &mode..."), CK_OptionsSaveMode)); + entries = g_list_prepend (entries, menu_entry_create (_("Learn &keys..."), CK_LearnKeys)); + entries = + g_list_prepend (entries, menu_entry_create (_("Syntax &highlighting..."), CK_SyntaxChoose)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("S&yntax file"), CK_EditSyntaxFile)); + entries = g_list_prepend (entries, menu_entry_create (_("&Menu file"), CK_EditUserMenu)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Save setup"), CK_SaveSetup)); + + return g_list_reverse (entries); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +edit_drop_menu_cmd (WDialog * h, int which) +{ + WMenuBar *menubar; + + menubar = find_menubar (h); + menubar_activate (menubar, drop_menus, which); +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +edit_init_menu (WMenuBar * menubar) +{ + menubar_add_menu (menubar, + create_menu (_("&File"), create_file_menu (), "[Internal File Editor]")); + menubar_add_menu (menubar, + create_menu (_("&Edit"), create_edit_menu (), "[Internal File Editor]")); + menubar_add_menu (menubar, + create_menu (_("&Search"), create_search_replace_menu (), + "[Internal File Editor]")); + menubar_add_menu (menubar, + create_menu (_("&Command"), create_command_menu (), + "[Internal File Editor]")); + menubar_add_menu (menubar, + create_menu (_("For&mat"), create_format_menu (), "[Internal File Editor]")); + menubar_add_menu (menubar, + create_menu (_("&Window"), create_window_menu (), "[Internal File Editor]")); + menubar_add_menu (menubar, + create_menu (_("&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..d289611 --- /dev/null +++ b/src/editor/editoptions.c @@ -0,0 +1,240 @@ +/* + Editor options dialog box + + Copyright (C) 1996-2022 + 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" +#include "src/setup.h" /* option_tab_spacing */ + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** 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))) + ((WEdit *) 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 = (WEdit *) 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", option_word_wrap_line_length); + g_snprintf (tab_spacing, sizeof (tab_spacing), "%d", option_tab_spacing); + + if (option_auto_para_formatting) + wrap_mode = 1; + else if (option_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"), &option_fake_half_tabs, NULL), + QUICK_CHECKBOX (N_("&Backspace through tabs"), &option_backspace_through_tabs, + NULL), + QUICK_CHECKBOX (N_("Fill tabs with &spaces"), &option_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"), &option_return_does_auto_indent, + NULL), + QUICK_CHECKBOX (N_("Confir&m before saving"), &edit_confirm_save, NULL), + QUICK_CHECKBOX (N_("Save file &position"), &option_save_position, NULL), + QUICK_CHECKBOX (N_("&Visible trailing spaces"), &visible_tws, NULL), + QUICK_CHECKBOX (N_("Visible &tabs"), &visible_tabs, NULL), + QUICK_CHECKBOX (N_("Synta&x highlighting"), &option_syntax_highlighting, NULL), + QUICK_CHECKBOX (N_("C&ursor after inserted block"), &option_cursor_after_inserted_block, NULL), + QUICK_CHECKBOX (N_("Pers&istent selection"), &option_persistent_selections, + NULL), + QUICK_CHECKBOX (N_("Cursor be&yond end of line"), &option_cursor_beyond_eol, + NULL), + QUICK_CHECKBOX (N_("&Group undo"), &option_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 = option_syntax_highlighting; + + if (!option_cursor_beyond_eol) + g_list_foreach (GROUP (h)->widgets, edit_reset_over_col, NULL); + + if (*p != '\0') + { + option_word_wrap_line_length = atoi (p); + if (option_word_wrap_line_length <= 0) + option_word_wrap_line_length = DEFAULT_WRAP_LINE_LENGTH; + g_free (p); + } + + if (*q != '\0') + { + option_tab_spacing = atoi (q); + if (option_tab_spacing <= 0) + option_tab_spacing = DEFAULT_TAB_SPACING; + g_free (q); + } + + if (wrap_mode == 1) + { + option_auto_para_formatting = TRUE; + option_typewriter_wrap = FALSE; + } + else if (wrap_mode == 2) + { + option_auto_para_formatting = FALSE; + option_typewriter_wrap = TRUE; + } + else + { + option_auto_para_formatting = FALSE; + option_typewriter_wrap = FALSE; + } + + /* Load or unload syntax rules if the option has changed */ + if (option_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..ba91d86 --- /dev/null +++ b/src/editor/editsearch.c @@ -0,0 +1,1034 @@ +/* + Search & replace engine of MCEditor. + + Copyright (C) 2021-2022 + 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 ****************************************************************/ + +/*** 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); + search_text = g_string_free (tmp, FALSE); + } +#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 + option_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->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..4493782 --- /dev/null +++ b/src/editor/editwidget.c @@ -0,0 +1,1548 @@ +/* + Editor initialisation and callback handler. + + Copyright (C) 1996-2022 + 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 ****************************************************************/ + +/*** file scope variables ************************************************************************/ +static unsigned int edit_dlg_init_refcounter = 0; + +/*** file scope functions ************************************************************************/ + +static cb_ret_t edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, + void *data); + +/* --------------------------------------------------------------------------------------------- */ +/** + * 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-2022 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 = create_listbox_window (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 = (WEdit *) 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 = run_listbox_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 = find_editor (h); + const char *modified = edit->modified ? "(*) " : " "; + const char *file_label; + char *filename; + + 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 ((WEdit *) g->current->data) && find_editor (h) == NULL) + dlg_stop (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) && ((WEdit *) w)->drag_state != MCEDIT_DRAG_NONE) + edit_restore_size ((WEdit *) w); + else if (command == CK_Quit) + dlg_stop (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 ((WEdit *) 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 = (WEdit *) 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 = (WEdit *) me->data; + + widget_select (WIDGET (e)); + + if (!edit_ok_to_exit (e)) + break; + } + + /* if all files were checked, quit editor */ + if (me == NULL) + dlg_stop (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 (!option_cursor_beyond_eol) + edit->prev_col = x - edit->start_col - option_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 - option_line_state_width; + edit->prev_col = line_len; + } + else + { + edit->over_col = 0; + edit->prev_col = x - option_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 (find_menubar (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 (find_menubar (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 = find_menubar (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)) + && ((WEdit *) 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 = (WEdit *) 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 = (WEdit *) w; + + switch (msg) + { + case MSG_FOCUS: + edit_set_buttonbar (e, find_buttonbar (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 + option_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 = (WEdit *) (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 = (WEdit *) 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 * +find_editor (const WDialog * h) +{ + const WGroup *g = CONST_GROUP (h); + + if (edit_widget_is_editor (CONST_WIDGET (g->current->data))) + return (WEdit *) g->current->data; + return (WEdit *) 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 (find_buttonbar (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, find_buttonbar (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..da0fb50 --- /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 higlighting */ + 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..d87fcc0 --- /dev/null +++ b/src/editor/etags.c @@ -0,0 +1,467 @@ +/* + 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-2022 + 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" /* canonicalize_pathname() */ + +#include "editwidget.h" + +#include "etags.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** 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); + canonicalize_pathname (def_hash->fullpath); + 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) + option_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 + option_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 maximim 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..1da94ec --- /dev/null +++ b/src/editor/format.c @@ -0,0 +1,539 @@ +/* + Dynamic paragraph formatting. + + Copyright (C) 2011-2022 + Free Software Foundation, Inc. + + Copyright (C) 1996 Paul Sheer + + Writen 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 <errno.h> +#include <sys/stat.h> + +#include <stdlib.h> + +#include "lib/global.h" + +#include "src/setup.h" /* option_tab_spacing */ + +#include "edit-impl.h" +#include "editwidget.h" + +/*** global variables ****************************************************************************/ + +char *option_stop_format_chars = NULL; + +/*** file scope macro definitions ****************************************************************/ + +#define tab_width option_tab_spacing + +#define FONT_MEAN_WIDTH 1 + +/*** file scope type declarations ****************************************************************/ + +/*** 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 (option_stop_format_chars != NULL && strchr (option_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_width - x % tab_width; + 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 conters */ + off_t char_length; /* character length in bytes */ + +#ifndef HAVE_CHARSET + (void) utf8; +#endif + + for (xn = 0, x = 0; xn <= l; x = xn, b += char_length) + { + char *tb; + + 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 = option_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 (!option_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 (option_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 (option_stop_format_chars != NULL + && strchr (option_stop_format_chars, t->str[0]) != NULL) + { + g_string_free (t, TRUE); + return; + } + + if (option_stop_format_chars == NULL || *option_stop_format_chars == '\0') + stop_format_chars = g_strdup ("\t"); + else + stop_format_chars = g_strconcat (option_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..1c8830c --- /dev/null +++ b/src/editor/spell.c @@ -0,0 +1,852 @@ +/* + Editor spell checker + + Copyright (C) 2012-2022 + 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/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; + +/*** 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 (GArray * 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) + { + char *tmp; + + tmp = g_strdup (entry->name); + g_array_append_val (lang_list, tmp); + i++; + } + } + + mc_delete_aspell_dict_info_enumeration (elem); + + return i; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Clear the array of languages. + * + * @param array Array of languages + */ + +void +aspell_array_clean (GArray * array) +{ + if (array != NULL) + { + guint i = 0; + + for (i = 0; i < array->len; ++i) + { + char *tmp; + + tmp = g_array_index (array, char *, i); + g_free (tmp); + } + g_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 (GArray * 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 = g_strdup (mc_aspell_string_enumeration_next (elements)); + if (cur_sugg_word != NULL) + g_array_append_val (suggest, 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 (!aspell_check (match_word->str, (int) word_len)) + { + GArray *suggest; + unsigned int res; + guint i; + + suggest = g_array_new (TRUE, FALSE, sizeof (char *)); + + 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) + { + char *cp_word; + +#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); + g_free (new_word); + new_word = g_string_free (tmp_word, FALSE); + } +#endif + cp_word = new_word; + for (i = 0; i < word_len; i++) + edit_backspace (edit, TRUE); + for (; *new_word; new_word++) + edit_insert (edit, *new_word); + g_free (cp_word); + } + else if (retval == B_ADD_WORD) + aspell_add_to_dict (match_word->str, (int) word_len); + } + + for (i = 0; i < suggest->len; i++) + { + char *cur_sugg_word; + + cur_sugg_word = g_array_index (suggest, char *, i); + g_free (cur_sugg_word); + } + g_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) +{ + GArray *lang_list; + + lang_list = g_array_new (TRUE, FALSE, sizeof (char *)); + if (aspell_get_lang_list (lang_list) != 0) + { + char *lang; + + lang = spell_dialog_lang_list_show (lang_list); + if (lang != NULL) + { + (void) aspell_set_lang (lang); + g_free (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, GArray * 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_array_index (suggest, char *, 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 choosed language + */ + +char * +spell_dialog_lang_list_show (GArray * languages) +{ + + int lang_dlg_h = 12; /* dialog height */ + int lang_dlg_w = 30; /* dialog width */ + char *selected_lang = NULL; + unsigned int i; + int res; + Listbox *lang_list; + + /* Create listbox */ + lang_list = create_listbox_window_centered (-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_array_index (languages, char *, i), NULL, FALSE); + + res = run_listbox (lang_list); + if (res >= 0) + selected_lang = g_strdup (g_array_index (languages, char *, (unsigned int) res)); + + return selected_lang; + +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/editor/spell.h b/src/editor/spell.h new file mode 100644 index 0000000..2b704f2 --- /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 (GArray * suggest, const char *word, const int word_size); +void aspell_array_clean (GArray * array); +unsigned int aspell_get_lang_list (GArray * 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, + GArray * suggest); +char *spell_dialog_lang_list_show (GArray * 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..323fa89 --- /dev/null +++ b/src/editor/syntax.c @@ -0,0 +1,1604 @@ +/* + Editor syntax highlighting. + + Copyright (C) 1996-2022 + 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 + * + * Mispelled 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 option_syntax_highlighting = TRUE; +gboolean option_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 +{ + char *keyword; + char *whole_word_chars_left; + char *whole_word_chars_right; + gboolean line_start; + int color; +} syntax_keyword_t; + +typedef struct +{ + char *left; + unsigned char first_left; + char *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; + +/*** 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_free (k->keyword); + 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_free (r->left); + g_free (r->right); + 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_foreach (r->keyword, (GFunc) syntax_keyword_free, 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 char *text, + const char *whole_left, const char *whole_right, gboolean line_start) +{ + const unsigned char *p, *q; + int c, d, j; + + if (*text == '\0') + return -1; + + 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, q = p + strlen ((const char *) p); 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[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 + && (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; + + count = p - r->keyword_first_chars; + k = SYNTAX_KEYWORD (g_ptr_array_index (r->keyword, count)); + 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[strlen (k->keyword) - 1] == '\n') + { + r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule._context)); + if (r->right != NULL && r->right[0] != '\0' + && r->right[strlen (r->right) - 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; + + 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; + + 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; + + count = p - r->keyword_first_chars; + k = SYNTAX_KEYWORD (g_ptr_array_index (r->keyword, count)); + 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 (); + + 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) + xx_lowerize_line (edit, l, len); + else + { + 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_strdup (" "); + c->right = g_strdup (" "); + } + 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_strdup (*a++); + check_a; + if (strcmp (*a, "linestart") == 0) + { + a++; + c->line_start_right = TRUE; + } + check_a; + c->right = g_strdup (*a++); + c->first_left = *c->left; + c->first_right = *c->right; + } + c->keyword = g_ptr_array_new (); + 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_strdup (" "); + 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_strdup (*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[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; + char *lib_file; + gboolean found = FALSE; + + f = fopen (syntax_file, "r"); + if (f == NULL) + { + lib_file = g_build_filename (mc_global.share_data_dir, EDIT_SYNTAX_FILE, (char *) NULL); + f = fopen (lib_file, "r"); + g_free (lib_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 = create_listbox_window (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_select_entry (syntaxlist->list, i + N_DFLT_ENTRIES); + } + + return run_listbox (syntaxlist); +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +int +edit_get_syntax_color (WEdit * edit, off_t byte_index) +{ + if (!tty_use_colors ()) + return 0; + + if (edit->rules != NULL && byte_index < edit->buffer.size && option_syntax_highlighting) + { + 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_foreach (edit->rules, (GFunc) context_rule_free, NULL); + 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 (option_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 (!option_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), + option_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 (); + + /* 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 = option_auto_syntax; + + switch (syntax) + { + case 0: /* auto syntax */ + option_auto_syntax = TRUE; + break; + case 1: /* reload current syntax */ + force_reload = TRUE; + break; + default: + option_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 || (option_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_foreach (names, (GFunc) g_free, NULL); + g_ptr_array_free (names, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ |