diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2015-05-25 22:02:31 +0200 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2015-05-25 22:02:31 +0200 |
commit | bae8cd712c167522a95a093296453a54dde4a524 (patch) | |
tree | 1523fcf738c9dbd0b6d96222ccd693faa2938060 /src/glob.cpp | |
parent | 83f18d2578288fe71fca409e4f03434346b0d5b7 (diff) | |
download | sciteco-bae8cd712c167522a95a093296453a54dde4a524.tar.gz |
extended <EN> command and used it to optimize "lexer.test..." macros
* EN may now be used for matching file names (similar to fnmatch(3)).
This is used to check the current buffers file extension in the
lexer configuration macros instead of using expensive Q-Register
manipulations.
This halves the overall startup time - it is now acceptable even
with the current amount of lexer configurations.
* EN may now be used for checking file types.
session.tes has been simplified.
* BREAKS macro portability (EN now has 2 string arguments).
* The Globber class has been extended to allow filtering of
glob results by file type.
Diffstat (limited to 'src/glob.cpp')
-rw-r--r-- | src/glob.cpp | 267 |
1 files changed, 230 insertions, 37 deletions
diff --git a/src/glob.cpp b/src/glob.cpp index 9ef3f89..66d6736 100644 --- a/src/glob.cpp +++ b/src/glob.cpp @@ -23,10 +23,13 @@ #include <glib.h> #include <glib/gprintf.h> +#include <glib/gstdio.h> #include "sciteco.h" #include "interface.h" #include "parser.h" +#include "expressions.h" +#include "qregisters.h" #include "ring.h" #include "ioview.h" #include "glob.h" @@ -34,10 +37,12 @@ namespace SciTECO { namespace States { - StateGlob glob; + StateGlob_pattern glob_pattern; + StateGlob_filename glob_filename; } -Globber::Globber(const gchar *pattern) +Globber::Globber(const gchar *pattern, GFileTest _test) + : test(_test) { gsize dirname_len; @@ -67,13 +72,27 @@ Globber::next(void) if (!dir) return NULL; - /* - * As dirname includes the directory separator, - * we can simply concatenate dirname with basename. - */ - while ((basename = g_dir_read_name(dir))) - if (g_pattern_match_string(pattern, basename)) - return g_strconcat(dirname, basename, NIL); + while ((basename = g_dir_read_name(dir))) { + gchar *filename; + + if (!g_pattern_match_string(pattern, basename)) + continue; + + /* + * As dirname includes the directory separator, + * we can simply concatenate dirname with basename. + */ + filename = g_strconcat(dirname, basename, NIL); + + /* + * No need to perform file test for EXISTS since + * g_dir_read_name() will only return existing entries + */ + if (test == G_FILE_TEST_EXISTS || g_file_test(filename, test)) + return filename; + + g_free(filename); + } return NULL; } @@ -92,32 +111,120 @@ Globber::~Globber() */ /*$ - * EN[pattern]$ -- Get list of files matching a glob pattern - * - * The EN command expands a glob \fIpattern\fP to a list of - * matching file names. This is similar to globbing - * on UNIX but not as powerful. - * The resulting file names have the exact same directory - * component as \fIpattern\fP (if any). + * [type]EN[pattern]$[filename]$ -- Glob files or match filename and check file type + * [type]:EN[pattern]$[filename]$ -> Success|Failure * + * EN is a powerful command for performing various tasks + * given a glob \fIpattern\fP. * A \fIpattern\fP is a file name with \(lq*\(rq and * \(lq?\(rq wildcards: * \(lq*\(rq matches an arbitrary, possibly empty, string. * \(lq?\(rq matches an arbitrary character. * - * EN will currently only match files in the file name component + * \fIpattern\fP may be omitted, in which case it defaults + * to the pattern saved in the search and glob register \(lq_\(rq. + * If it is specified, it overwrites the contents of the register + * \(lq_\(rq with \fIpattern\fP. + * This behaviour is similar to the search and replace commands + * and allows for repeated globbing/matching with the same + * pattern. + * Therefoe you should also save the \(lq_\(rq register on the + * Q-Register stack when calling EN from portable macros. + * + * If \fIfilename\fP is omitted (empty), EN may be used to expand + * a glob \fIpattern\fP to a list of matching file names. + * This is similar to globbing + * on UNIX but not as powerful and may be used e.g. for + * iterating over directory contents. + * E.g. \(lqEN*.c\fB$$\fP\(rq expands to all \(lq.c\(rq files + * in the current directory. + * The resulting file names have the exact same directory + * component as \fIpattern\fP (if any). + * Without \fIfilename\fP, EN will currently only match files + * in the file name component * of \fIpattern\fP, not on each component of the path name * separately. * In other words, EN only looks through the directory * of \fIpattern\fP \(em you cannot effectively match * multiple directories. * - * The result of the globbing is inserted into the current + * If \fIfilename\fP is specified, \fIpattern\fP will only + * be matched against that single file name. + * If it matches, \fIfilename\fP is used verbatim. + * In this form, \fIpattern\fP is matched against the entire + * file name, so it is possible to match directory components + * as well. + * \fIfilename\fP does not necessarily have to exist in the + * file system for the match to succeed (unless a file type check + * is also specified). + * For instance, \(lqENf??/\[**].c\fB$\fPfoo/bar.c\fB$\fP\(rq will + * always match and the string \(lqfoo/bar.c\(rq will be inserted + * (see below). + * + * By default, if EN is not colon-modified, the result of + * globbing or file name matching is inserted into the current * document, at the current position. * A linefeed is inserted after every file name, i.e. * every matching file will be on its own line. * - * String-building characters are enabled for EN. + * EN may be colon-modified to avoid any text insertion. + * Instead, a boolean is returned that signals whether + * any file matched \fIpattern\fP. + * E.g. \(lq:EN*.c\fB$$\fP\(rq returns success (-1) if + * there is at least one \(lq.c\(rq file in the current directory. + * + * The results of EN may be filtered by specifying a numeric file + * \fItype\fP check argument. + * This argument may be omitted (as in the examples above) and defaults + * to 0, i.e. no additional checking. + * The following file type check values are currently defined: + * .IP 0 4 + * No file type checking is performed. + * Note however, that when globbing only directory contents + * (of any type) are used, so without the \fIfilename\fP + * argument, the value 0 is equivalent to 5. + * .IP 1 + * Only match \fIregular files\fP (no directories). + * Will also match symlinks to regular files (on platforms + * supporting symlinks). + * .IP 2 + * Only match \fIsymlinks\fP. + * On platforms without symlinks (non-UNIX), this will never + * match anything. + * .IP 3 + * Only match \fIdirectories\fP. + * .IP 4 + * Only match \fIexecutables\fP. + * On UNIX, the executable flag is evaluated, while on + * Windows only the file name is checked. + * .IP 5 + * Only match existing files or directories. + * When globbing, this check makes no sense and is + * equivalent to no check at all. + * It may however be used to test that a filename refers + * to an existing file. + * + * For instance, \(lq3EN*\fB$$\fP\(rq will expand to + * all subdirectories in the current directory. + * The following idiom may be used to check whether + * a given filename refers to a regular file: + * 1:EN*\fB$\fIfilename\fB$\fR + * + * Note that both without colon and colon modified + * forms of EN save the success or failure of the + * operation in the numeric part of the glob register + * \(lq_\(rq (i.e. the same value that the colon modified + * form would return). + * The command itself never fails because of failure + * in matching any files. + * E.g. if \(lqEN*.c\fB$$\fP\(rq does not match any + * files, the EN command is still successful but does + * not insert anything. A failure boolean would be saved + * in \(lq_\(rq, though. + * + * String-building characters are enabled for EN and + * both string arguments are considered file names + * with regard to auto-completions. */ /* * NOTE: This does not work like classic TECO's @@ -131,43 +238,129 @@ Globber::~Globber() * have to edit that register anyway. */ State * -StateGlob::done(const gchar *str) +StateGlob_pattern::done(const gchar *str) +{ + BEGIN_EXEC(&States::glob_filename); + + if (*str) { + QRegister *glob_reg = QRegisters::globals["_"]; + + glob_reg->undo_set_string(); + glob_reg->set_string(str); + } + + return &States::glob_filename; +} + +State * +StateGlob_filename::done(const gchar *str) { BEGIN_EXEC(&States::start); - Globber globber(str); + tecoInt teco_test_mode; + GFileTest file_flags = G_FILE_TEST_EXISTS; + + bool matching = false; + bool colon_modified = eval_colon(); + + QRegister *glob_reg = QRegisters::globals["_"]; + gchar *pattern_str; - gchar *filename; - bool text_added = false; + expressions.eval(); + teco_test_mode = expressions.pop_num_calc(0, 0); + switch (teco_test_mode) { + /* + * 0 means, no file testing. + * file_flags will still be G_FILE_TEST_EXISTS which + * is equivalent to no testing when using the Globber class. + */ + case 0: break; + case 1: file_flags = G_FILE_TEST_IS_REGULAR; break; + case 2: file_flags = G_FILE_TEST_IS_SYMLINK; break; + case 3: file_flags = G_FILE_TEST_IS_DIR; break; + case 4: file_flags = G_FILE_TEST_IS_EXECUTABLE; break; + case 5: file_flags = G_FILE_TEST_EXISTS; break; + default: + throw Error("Invalid file test %" TECO_INTEGER_FORMAT + " for <EN>", teco_test_mode); + } - interface.ssm(SCI_BEGINUNDOACTION); + pattern_str = glob_reg->get_string(); - while ((filename = globber.next())) { - size_t len = strlen(filename); - /* overwrite trailing null */ - filename[len] = '\n'; + if (*str) { + /* + * Match pattern against provided file name + */ + if (g_pattern_match_simple(pattern_str, str) && + (!teco_test_mode || g_file_test(str, file_flags))) { + if (!colon_modified) { + interface.ssm(SCI_BEGINUNDOACTION); + interface.ssm(SCI_ADDTEXT, strlen(str), + (sptr_t)str); + interface.ssm(SCI_ADDTEXT, 1, (sptr_t)"\n"); + interface.ssm(SCI_SCROLLCARET); + interface.ssm(SCI_ENDUNDOACTION); + } + matching = true; + } + } else if (colon_modified) { /* - * FIXME: Once we're 8-bit clean, we should - * add the filenames null-terminated - * (there may be linebreaks in filename). + * Match pattern against directory contents (globbing), + * returning SUCCESS if at least one file matches */ - interface.ssm(SCI_ADDTEXT, len+1, - (sptr_t)filename); + Globber globber(pattern_str, file_flags); + gchar *filename = globber.next(); + + matching = filename != NULL; g_free(filename); - text_added = true; + } else { + /* + * Match pattern against directory contents (globbing), + * inserting all matching file names (linefeed-terminated) + */ + Globber globber(pattern_str, file_flags); + + gchar *filename; + + interface.ssm(SCI_BEGINUNDOACTION); + + while ((filename = globber.next())) { + size_t len = strlen(filename); + /* overwrite trailing null */ + filename[len] = '\n'; + + /* + * FIXME: Once we're 8-bit clean, we should + * add the filenames null-terminated + * (there may be linebreaks in filename). + */ + interface.ssm(SCI_ADDTEXT, len+1, + (sptr_t)filename); + + g_free(filename); + matching = true; + } + + interface.ssm(SCI_SCROLLCARET); + interface.ssm(SCI_ENDUNDOACTION); } - interface.ssm(SCI_SCROLLCARET); - interface.ssm(SCI_ENDUNDOACTION); + g_free(pattern_str); - if (text_added) { + if (colon_modified) { + expressions.push(TECO_BOOL(matching)); + } else if (matching) { + /* text has been inserted */ ring.dirtify(); if (current_doc_must_undo()) interface.undo_ssm(SCI_UNDO); } + glob_reg->undo_set_integer(); + glob_reg->set_integer(TECO_BOOL(matching)); + return &States::start; } |