aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--doc/sciteco.7.template82
-rw-r--r--src/cmdline.cpp26
-rw-r--r--src/glob.cpp36
-rw-r--r--src/glob.h17
-rw-r--r--src/ioview.cpp27
-rw-r--r--src/ioview.h2
-rw-r--r--src/parser.cpp34
-rw-r--r--src/parser.h8
-rw-r--r--src/qregisters.cpp14
-rw-r--r--src/qregisters.h6
-rw-r--r--src/ring.cpp22
-rw-r--r--src/ring.h4
12 files changed, 203 insertions, 75 deletions
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index 1e3739a..db31daf 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -582,16 +582,13 @@ to backslash.
Therefore forward-slash auto-completion is possible on Windows,
e.g. by using a forward-slash in the file name or by prefixing
file names with \(lq./\(rq.
-This is useful for writing cross-platform \*(ST macros since
-on other platforms (notably UNIX), only forward slashes are
-accepted in file names and directories.
+This is useful for writing cross-platform \*(ST macros (see
+.BR "FILE NAMES AND DIRECTORIES" ).
.LP
-Note that no additional expansions are performed before attempting
-a completion, so for instance \(lq~/foo\(rq will not complete a file
-in the user's home directory (tilde is not part of the file name but
-tilde-expansions are performed by the shell).
-\*(ST does however perform completions after string building so that
-\(lq^EQ[$HOME]/foo\(rq could be completed.
+Note that completions take place after string building and
+tilde-expansion is also performed by file name completions,
+so for instance \(lq~/foo\(rq will complete a file
+in the user's home directory.
.
.
.SH ARITHMETICS AND EXPRESSIONS
@@ -1287,6 +1284,73 @@ Note however that currently, all pattern matching is performed
.BR case-insensitive .
.
.
+.SH FILE NAMES AND DIRECTORIES
+.
+One class of \*(ST commands accepts file names or directory
+path arguments.
+The most prominent is the \fBEB\fP command.
+All file names or directories can be absolute or relative
+paths.
+Relative paths will be resolved according to the process'
+current working directory (as can be set e.g. via the
+\fBFG\fP command).
+Nevertheless, \*(ST will function properly after changing
+the working directory as \*(ST canonicalizes relative
+paths to absolute paths if necessary.
+Both buffer file names and some special environment
+variables (as well as their corresponding Q-Registers)
+documented in
+.BR sciteco (1)
+are canonicalized in this way.
+.LP
+The directory separator style (forward or backslash) accepted
+by \*(ST commands is platform dependent.
+Forward slash directory separators can be used on every supported
+platform.
+On Windows, both forward and backslash directory separators
+are accepted in order to faciliate a native look and feel.
+Since on some supported platforms (notably UNIX), forward slash
+directory separators are the only supported separator style,
+they are recommended when writing cross-platform \*(ST macros.
+File names queried via some \*(ST command or Q-Register
+are also normalized to contain only forward slash directory
+separators (on platforms supporting both styles) in order to
+ease the task of cross-platform macros.
+.LP
+Currently all path name arguments also support string building
+characters.
+Therefore it is possible to refer to environment variables
+in path arguments.
+For instance, \(lq^EQ[$HOME]/.teco_ini\(rq will refer to the
+\*(ST profile on UNIX by default.
+.LP
+Even though the \(lqHOME\(rq environment variable is initialized
+to a sane value by \*(ST, it is cumbersome to type in frequently
+used commands.
+Therefore, \*(ST also supports UNIX-shell-like tilde-expansions.
+So for instance, the file name \(lq~/.teco_ini\(rq also expands
+to the \*(ST profile on UNIX by default and is roughly equivalent
+to \(lq^EQ[$HOME]/.teco_ini\(rq.
+It is important to note that \(lq~\(rq is not part of the file
+name proper (not even on UNIX) but a token that needs to be
+expanded first.
+In \*(ST this expansion takes place \fIafter\fP processing
+string building characters.
+Unlike the UNIX-shell, \*(ST will only expand the \fIcurrent
+user's\fP home directory using the value of the
+\(lqHOME\(rq environment variable.
+Thus the \(lq~\fIusername\fP\(rq syntax is \fBnot\fP supported.
+.LP
+Last but not least, some commands accept glob patterns
+in their file name arguments to refer to multiple files at once
+(see \fBEN\fP command).
+There is special immediate editing command support for
+file and directory path arguments (e.g. tab-completions and
+specialized rub-out/rub-in commands).
+These are documented in the section
+.BR "COMMANDLINE EDITING" .
+.
+.
.SH FLOW CONTROL
.
\*(ST is a structured imperative language.
diff --git a/src/cmdline.cpp b/src/cmdline.cpp
index 82c8434..94e68ba 100644
--- a/src/cmdline.cpp
+++ b/src/cmdline.cpp
@@ -592,24 +592,24 @@ static gchar *
filename_complete(const gchar *filename, gchar completed,
GFileTest file_test)
{
+ gchar *filename_expanded;
+ gsize filename_len;
+ gchar *dirname, *basename, dir_sep;
gsize dirname_len;
- gchar *dirname, dir_sep;
- const gchar *basename, *cur_basename;
+ const gchar *cur_basename;
GDir *dir;
GSList *files = NULL;
guint files_len = 0;
gchar *insert = NULL;
- gsize filename_len;
gsize prefix_len = 0;
- if (!filename)
- filename = "";
- filename_len = strlen(filename);
-
if (is_glob_pattern(filename))
return NULL;
+ filename_expanded = expand_path(filename);
+ filename_len = strlen(filename_expanded);
+
/*
* Derive base and directory names.
* We do not use g_path_get_basename() or g_path_get_dirname()
@@ -617,13 +617,14 @@ filename_complete(const gchar *filename, gchar completed,
* in order to construct paths of entries in dirname
* that are suitable for auto completion.
*/
- dirname_len = file_get_dirname_len(filename);
- dirname = g_strndup(filename, dirname_len);
- basename = filename + dirname_len;
+ dirname_len = file_get_dirname_len(filename_expanded);
+ dirname = g_strndup(filename_expanded, dirname_len);
+ basename = filename_expanded + dirname_len;
dir = g_dir_open(dirname_len ? dirname : ".", 0, NULL);
if (!dir) {
g_free(dirname);
+ g_free(filename_expanded);
return NULL;
}
@@ -632,7 +633,7 @@ filename_complete(const gchar *filename, gchar completed,
* directory separators are allowed in directory
* names passed to glib.
* To imitate glib's behaviour, we use
- * the last valid directory separator in `filename`
+ * the last valid directory separator in `filename_expanded`
* to generate new separators.
* This also allows forward-slash auto-completion
* on Windows.
@@ -684,8 +685,9 @@ filename_complete(const gchar *filename, gchar completed,
if (prefix_len > 0)
insert = g_strndup((gchar *)files->data + filename_len, prefix_len);
- g_free(dirname);
g_dir_close(dir);
+ g_free(dirname);
+ g_free(filename_expanded);
if (!insert && files_len > 1) {
files = g_slist_sort(files, (GCompareFunc)g_strcmp0);
diff --git a/src/glob.cpp b/src/glob.cpp
index 66d6736..4c1fa1f 100644
--- a/src/glob.cpp
+++ b/src/glob.cpp
@@ -238,22 +238,22 @@ Globber::~Globber()
* have to edit that register anyway.
*/
State *
-StateGlob_pattern::done(const gchar *str)
+StateGlob_pattern::got_file(const gchar *filename)
{
BEGIN_EXEC(&States::glob_filename);
- if (*str) {
+ if (*filename) {
QRegister *glob_reg = QRegisters::globals["_"];
glob_reg->undo_set_string();
- glob_reg->set_string(str);
+ glob_reg->set_string(filename);
}
return &States::glob_filename;
}
State *
-StateGlob_filename::done(const gchar *str)
+StateGlob_filename::got_file(const gchar *filename)
{
BEGIN_EXEC(&States::start);
@@ -287,16 +287,16 @@ StateGlob_filename::done(const gchar *str)
pattern_str = glob_reg->get_string();
- if (*str) {
+ if (*filename) {
/*
* Match pattern against provided file name
*/
- if (g_pattern_match_simple(pattern_str, str) &&
- (!teco_test_mode || g_file_test(str, file_flags))) {
+ if (g_pattern_match_simple(pattern_str, filename) &&
+ (!teco_test_mode || g_file_test(filename, file_flags))) {
if (!colon_modified) {
interface.ssm(SCI_BEGINUNDOACTION);
- interface.ssm(SCI_ADDTEXT, strlen(str),
- (sptr_t)str);
+ interface.ssm(SCI_ADDTEXT, strlen(filename),
+ (sptr_t)filename);
interface.ssm(SCI_ADDTEXT, 1, (sptr_t)"\n");
interface.ssm(SCI_SCROLLCARET);
interface.ssm(SCI_ENDUNDOACTION);
@@ -310,11 +310,11 @@ StateGlob_filename::done(const gchar *str)
* returning SUCCESS if at least one file matches
*/
Globber globber(pattern_str, file_flags);
- gchar *filename = globber.next();
+ gchar *globbed_filename = globber.next();
- matching = filename != NULL;
+ matching = globbed_filename != NULL;
- g_free(filename);
+ g_free(globbed_filename);
} else {
/*
* Match pattern against directory contents (globbing),
@@ -322,14 +322,14 @@ StateGlob_filename::done(const gchar *str)
*/
Globber globber(pattern_str, file_flags);
- gchar *filename;
+ gchar *globbed_filename;
interface.ssm(SCI_BEGINUNDOACTION);
- while ((filename = globber.next())) {
- size_t len = strlen(filename);
+ while ((globbed_filename = globber.next())) {
+ size_t len = strlen(globbed_filename);
/* overwrite trailing null */
- filename[len] = '\n';
+ globbed_filename[len] = '\n';
/*
* FIXME: Once we're 8-bit clean, we should
@@ -337,9 +337,9 @@ StateGlob_filename::done(const gchar *str)
* (there may be linebreaks in filename).
*/
interface.ssm(SCI_ADDTEXT, len+1,
- (sptr_t)filename);
+ (sptr_t)globbed_filename);
- g_free(filename);
+ g_free(globbed_filename);
matching = true;
}
diff --git a/src/glob.h b/src/glob.h
index a289228..75361b8 100644
--- a/src/glob.h
+++ b/src/glob.h
@@ -18,8 +18,6 @@
#ifndef __GLOB_H
#define __GLOB_H
-#include <string.h>
-
#include <glib.h>
#include <glib/gstdio.h>
@@ -34,7 +32,16 @@ namespace SciTECO {
static inline bool
is_glob_pattern(const gchar *str)
{
- return strchr(str, '*') || strchr(str, '?');
+ if (!str)
+ return false;
+
+ while (*str) {
+ if (*str == '*' || *str == '?')
+ return true;
+ str++;
+ }
+
+ return false;
}
class Globber {
@@ -60,12 +67,12 @@ public:
StateGlob_pattern() : StateExpectFile(true, false) {}
private:
- State *done(const gchar *str);
+ State *got_file(const gchar *filename);
};
class StateGlob_filename : public StateExpectFile {
private:
- State *done(const gchar *str);
+ State *got_file(const gchar *filename);
};
namespace States {
diff --git a/src/ioview.cpp b/src/ioview.cpp
index 323377f..e43fc2c 100644
--- a/src/ioview.cpp
+++ b/src/ioview.cpp
@@ -669,6 +669,33 @@ IOView::save(const gchar *filename)
/*
* Auxiliary functions
*/
+
+/**
+ * Perform tilde expansion on a file name or path.
+ *
+ * This supports only strings with a "~" prefix.
+ * A user name after "~" is not supported.
+ * The $HOME environment variable is used to retrieve
+ * the current user's home directory.
+ */
+gchar *
+expand_path(const gchar *path)
+{
+ if (!path)
+ path = "";
+
+ if (path[0] != '~' || (path[1] && !G_IS_DIR_SEPARATOR(path[1])))
+ return g_strdup(path);
+
+ /*
+ * $HOME should not have a trailing directory separator since
+ * it is canonicalized to an absolute path at startup,
+ * but this ensures that a proper path is constructed even if
+ * it does (e.g. $HOME is changed later on).
+ */
+ return g_build_filename(g_getenv("HOME"), path+1, NIL);
+}
+
#ifdef G_OS_UNIX
gchar *
diff --git a/src/ioview.h b/src/ioview.h
index d9d8265..e0f23f4 100644
--- a/src/ioview.h
+++ b/src/ioview.h
@@ -34,6 +34,8 @@ namespace SciTECO {
* Auxiliary functions
*/
+gchar *expand_path(const gchar *path);
+
/**
* Get absolute/full version of a possibly relative path.
* Works with existing and non-existing paths (in the latter case,
diff --git a/src/parser.cpp b/src/parser.cpp
index 6462933..3e3f387 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -40,6 +40,7 @@
#include "spawn.h"
#include "glob.h"
#include "cmdline.h"
+#include "ioview.h"
#include "error.h"
namespace SciTECO {
@@ -473,6 +474,7 @@ StateExpectString::custom(gchar chr)
if (string_building)
machine.reset();
+ /* FIXME: possible memleak because of `string`? */
next = done(string ? : "");
g_free(string);
return next;
@@ -502,6 +504,23 @@ StateExpectString::custom(gchar chr)
return this;
}
+State *
+StateExpectFile::done(const gchar *str)
+{
+ gchar *filename = expand_path(str);
+ State *next;
+
+ try {
+ next = got_file(filename);
+ } catch (...) {
+ g_free(filename);
+ throw;
+ }
+
+ g_free(filename);
+ return next;
+}
+
StateStart::StateStart() : State()
{
transitions['\0'] = this;
@@ -1579,8 +1598,9 @@ UndoTokenChangeDir::run(void)
* as set by the \fBHOME\fP environment variable.
* This variable is alwas initialized by \*(ST
* (see \fBsciteco\fP(1)).
- * Therefore \(lqFG\fB$\fP\(rq is roughly equivalent
- * to \(lqFG^EQ[$HOME]\fB$\fP\(rq.
+ * Therefore the expression \(lqFG\fB$\fP\(rq is
+ * roughly equivalent to both \(lqFG~\fB$\fP\(rq and
+ * \(lqFG^EQ[$HOME]\fB$\fP\(rq.
*
* The current working directory is also mapped to
* the Q-Register \(lq$\(rq (dollar sign) which
@@ -1590,20 +1610,20 @@ UndoTokenChangeDir::run(void)
* command and directories can be tab-completed.
*/
State *
-StateChangeDir::done(const gchar *str)
+StateChangeDir::got_file(const gchar *filename)
{
BEGIN_EXEC(&States::start);
/* passes ownership of string to undo token object */
undo.push(new UndoTokenChangeDir(g_get_current_dir()));
- if (!*str)
- str = g_getenv("HOME");
+ if (!*filename)
+ filename = g_getenv("HOME");
- if (g_chdir(str))
+ if (g_chdir(filename))
/* FIXME: Is errno usable on Windows here? */
throw Error("Cannot change working directory "
- "to \"%s\"", str);
+ "to \"%s\"", filename);
return &States::start;
}
diff --git a/src/parser.h b/src/parser.h
index 605c1a5..feffb95 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -160,6 +160,12 @@ class StateExpectFile : public StateExpectString {
public:
StateExpectFile(bool _building = true, bool _last = true)
: StateExpectString(_building, _last) {}
+
+private:
+ State *done(const gchar *str);
+
+protected:
+ virtual State *got_file(const gchar *filename) = 0;
};
class StateExpectDir : public StateExpectFile {
@@ -236,7 +242,7 @@ public:
class StateChangeDir : public StateExpectDir {
private:
- State *done(const gchar *str);
+ State *got_file(const gchar *filename);
};
class StateCondCommand : public State {
diff --git a/src/qregisters.cpp b/src/qregisters.cpp
index f15f0d7..20c1380 100644
--- a/src/qregisters.cpp
+++ b/src/qregisters.cpp
@@ -831,13 +831,13 @@ StateEQCommand::got_register(QRegister &reg)
}
State *
-StateLoadQReg::done(const gchar *str)
+StateLoadQReg::got_file(const gchar *filename)
{
BEGIN_EXEC(&States::start);
- if (*str) {
+ if (*filename) {
/* Load file into Q-Register */
- register_argument->load(str);
+ register_argument->load(filename);
} else {
/* Edit Q-Register */
current_doc_undo_edit();
@@ -871,10 +871,10 @@ StateEPctCommand::got_register(QRegister &reg)
}
State *
-StateSaveQReg::done(const gchar *str)
+StateSaveQReg::got_file(const gchar *filename)
{
BEGIN_EXEC(&States::start);
- register_argument->save(str);
+ register_argument->save(filename);
return &States::start;
}
@@ -1158,12 +1158,12 @@ StateMacro::got_register(QRegister &reg)
* If <file> could not be read, the command yields an error.
*/
State *
-StateMacroFile::done(const gchar *str)
+StateMacroFile::got_file(const gchar *filename)
{
BEGIN_EXEC(&States::start);
/* don't create new local Q-Registers if colon modifier is given */
- Execute::file(str, !eval_colon());
+ Execute::file(filename, !eval_colon());
return &States::start;
}
diff --git a/src/qregisters.h b/src/qregisters.h
index 2933dc8..ec40bb4 100644
--- a/src/qregisters.h
+++ b/src/qregisters.h
@@ -420,7 +420,7 @@ private:
class StateLoadQReg : public StateExpectFile {
private:
- State *done(const gchar *str);
+ State *got_file(const gchar *filename);
};
class StateEPctCommand : public StateExpectQReg {
@@ -430,7 +430,7 @@ private:
class StateSaveQReg : public StateExpectFile {
private:
- State *done(const gchar *str);
+ State *got_file(const gchar *filename);
};
class StateQueryQReg : public StateExpectQReg {
@@ -494,7 +494,7 @@ private:
class StateMacroFile : public StateExpectFile {
private:
- State *done(const gchar *str);
+ State *got_file(const gchar *filename);
};
class StateCopyToQReg : public StateExpectQReg {
diff --git a/src/ring.cpp b/src/ring.cpp
index 906e72a..4646ea1 100644
--- a/src/ring.cpp
+++ b/src/ring.cpp
@@ -355,26 +355,26 @@ StateEditFile::initial(void)
}
State *
-StateEditFile::done(const gchar *str)
+StateEditFile::got_file(const gchar *filename)
{
BEGIN_EXEC(&States::start);
if (!allowFilename) {
- if (*str)
+ if (*filename)
throw Error("If a buffer is selected by id, the <EB> "
"string argument must be empty");
return &States::start;
}
- if (is_glob_pattern(str)) {
- Globber globber(str, G_FILE_TEST_IS_REGULAR);
- gchar *filename;
+ if (is_glob_pattern(filename)) {
+ Globber globber(filename, G_FILE_TEST_IS_REGULAR);
+ gchar *globbed_filename;
- while ((filename = globber.next()))
- do_edit(filename);
+ while ((globbed_filename = globber.next()))
+ do_edit(globbed_filename);
} else {
- do_edit(*str ? str : NULL);
+ do_edit(*filename ? filename : NULL);
}
return &States::start;
@@ -422,14 +422,14 @@ StateEditFile::done(const gchar *str)
* characters are enabled by default.
*/
State *
-StateSaveFile::done(const gchar *str)
+StateSaveFile::got_file(const gchar *filename)
{
BEGIN_EXEC(&States::start);
if (QRegisters::current)
- QRegisters::current->save(str);
+ QRegisters::current->save(filename);
else
- ring.current->save(*str ? str : NULL);
+ ring.current->save(*filename ? filename : NULL);
return &States::start;
}
diff --git a/src/ring.h b/src/ring.h
index d2e9d7b..0f82e3f 100644
--- a/src/ring.h
+++ b/src/ring.h
@@ -223,12 +223,12 @@ private:
void do_edit(tecoInt id);
void initial(void);
- State *done(const gchar *str);
+ State *got_file(const gchar *filename);
};
class StateSaveFile : public StateExpectFile {
private:
- State *done(const gchar *str);
+ State *got_file(const gchar *filename);
};
namespace States {