aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2015-06-02 15:38:00 +0200
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2015-06-02 15:38:00 +0200
commit02d414b3ab447e637fef776e387aa5a07293738a (patch)
tree60d5db01e9e0cc15f1f8e84abb14138cb0ca54a3
parent6c62ccc436d972a872616c187d460ed61c8bc0d2 (diff)
downloadsciteco-02d414b3ab447e637fef776e387aa5a07293738a.tar.gz
added <FG> command and special Q-Register "$" to set and get the current working directory
* FG stands for "Folder Go" * FG behaves similar to a Unix shell `cd`. Without arguments, it changes to the $HOME directory. * The $HOME directory was previously only used by $SCITECOCONFIG on Unix. Now it is documented on its own, since the HOME directory should also be configurable on Windows - e.g. to adapt SciTECO to a MinGW or Cygwin installation. HOME is initialized just like the other environment variables. This also means that now, the $HOME Q-Register is always defined and can be used by platform-agnostic macros. * FG uses a new kind of tab-completion: for directories only. It would be annoying to complete the FG command after every directory, so this tab-completion does not close the command automatically. Theoretically, it would be possible to close the command after completing a directory with no subdirectories, but this is not supported currently. * Filename arguments are no longer completed with " " if {} escaping is in place as this brings no benefit. Instead no completion character is inserted for this escape mode. * "$" was mapped to the current directory to support an elegant way to insert/get the current directory. Also this allows the idiom "[$ FG...new_dir...$ ]$" for changing the current directory temporarily. * The Q-Register stack was extended to support restoring the string part of special Q-Registers (that overwrite the default functionality) when using the "[$" and "]$" commands. * fixed minor typos (american spelling)
-rw-r--r--doc/sciteco.1.in25
-rw-r--r--doc/sciteco.7.template27
-rw-r--r--src/cmdline.cpp42
-rw-r--r--src/ioview.cpp2
-rw-r--r--src/ioview.h23
-rw-r--r--src/main.cpp16
-rw-r--r--src/parser.cpp56
-rw-r--r--src/parser.h46
-rw-r--r--src/qregisters.cpp118
-rw-r--r--src/qregisters.h31
-rw-r--r--src/spawn.cpp4
11 files changed, 360 insertions, 30 deletions
diff --git a/doc/sciteco.1.in b/doc/sciteco.1.in
index 3129189..0f091db 100644
--- a/doc/sciteco.1.in
+++ b/doc/sciteco.1.in
@@ -145,23 +145,32 @@ batch mode processing.
.SH ENVIRONMENT
.
Before \*(ST executes any macro, all of the variables in the process
-environment are inserted into the global
-.I Q-Register
-table.
+environment are inserted into the global Q-Register table.
A dollar sign is prepended before each variable name, so that for
instance the variable \(lqHOME\(rq can be examined by macros by
reading the string-content of Q-Register \(lq$HOME\(rq.
+Changes to these Q-Registers are currently not applied to
+the corresponding environment variables.
+.LP
The following environment variables are initialized with default
values by \*(ST if they are unset:
.TP
+.B HOME
+Home directory of the current user.
+This may be used e.g. by the \fBFG\fP command.
+If unset, it defaults to the current user's home directory
+as set by
+.BR passwd (5)
+or as determined by other platform-dependent means.
+Initialization of this variable ensures that the
+\(lq$HOME\(rq Q-Register is available even on Windows
+and the home directory can always be re-configured.
+.TP
.B SCITECOCONFIG
Path where \*(ST looks for configuration files.
For instance, this is the path of the default profile.
If unset, this variable defaults to the \fBHOME\fP
-environment variable on UNIX/Linux.
-If \fBHOME\fP is unset, it defaults to the current user's
-home directory as set by
-.BR passwd (5).
+environment variable on Unix.
On Windows, this variable defaults to the location of the
\*(ST program executable, so that \*(ST is self-contained
on Windows.
@@ -181,7 +190,7 @@ defaults to the standard library installation path at
.BR "@scitecolibdir@" .
.
.LP
-Both the \fBSCITECOCONFIG\fP and \fBSCITECOPATH\fP environment
+The \fBHOME\fP, \fBSCITECOCONFIG\fP and \fBSCITECOPATH\fP environment
variables are canonicalized to absolute paths.
Therefore it is possible to define them relative to the
working directory of \*(ST when it starts up while macros
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index 4fccd33..8272b08 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -443,6 +443,13 @@ Fully completed filenames terminate the string argument,
except if the \(lq{\(rq terminator is used.
T}
T{
+Auto complete directory
+T};9;^I, Tab;T{
+Directory arguments
+T};T{
+Auto complete directory beginning at the start of the argument.
+T}
+T{
Auto complete symbol
T};9;^I, Tab;T{
Scintilla symbol arguments
@@ -1022,6 +1029,26 @@ temporarily:
[* ! ...change current buffer... ! ]*
.EE
.TP
+.BR $ " (dollar)"
+The process' current working directory (string part).
+Its numeric part is currently unused.
+The working directory will always be returned as an
+absolute path with normalized forward-slash directory
+separators.
+It is possible to set this register using the \fB^U\fP or
+\fBEU\fP commands which will change the current working
+directory in a manner similar to the \fBFG\fP command.
+This allows you to change the current directory temporarily
+with the following idiom:
+.EX
+[$ ! ...change current directory... ! ]$
+.EE
+As with \fBFG\fP, relative directories may be specified
+but querying \(lq$\(rq will still return an absolute path.
+The \(lq$\(rq register may also be edited but changing its
+string contents this way has no effect on the current
+working directory.
+.TP
.BR $ " (Escape)"
Command-line replacement register.
Its integer part is unused.
diff --git a/src/cmdline.cpp b/src/cmdline.cpp
index b85850a..82c8434 100644
--- a/src/cmdline.cpp
+++ b/src/cmdline.cpp
@@ -48,7 +48,8 @@
namespace SciTECO {
-static gchar *filename_complete(const gchar *filename, gchar completed = ' ');
+static gchar *filename_complete(const gchar *filename, gchar completed = ' ',
+ GFileTest file_test = G_FILE_TEST_EXISTS);
static gchar *symbol_complete(SymbolList &list, const gchar *symbol,
gchar completed = ' ');
@@ -291,6 +292,7 @@ Cmdline::process_edit_cmd(gchar key)
interface.popup_clear();
if (States::is_file()) {
+ /* File names, including directories */
if (modifier_enabled) {
/* reinsert one level of file name */
while (States::is_file() && rubout_len &&
@@ -416,6 +418,19 @@ Cmdline::process_edit_cmd(gchar key)
while (spaces--)
insert(' ');
+ } else if (States::is_dir()) {
+ if (interface.popup_is_shown()) {
+ /* cycle through popup pages */
+ interface.popup_show();
+ break;
+ }
+
+ gchar *new_chars = filename_complete(strings[0], '\0',
+ G_FILE_TEST_IS_DIR);
+
+ if (new_chars)
+ insert(new_chars);
+ g_free(new_chars);
} else if (States::is_file()) {
if (interface.popup_is_shown()) {
/* cycle through popup pages */
@@ -423,7 +438,7 @@ Cmdline::process_edit_cmd(gchar key)
break;
}
- gchar complete = escape_char == '{' ? ' ' : escape_char;
+ gchar complete = escape_char == '{' ? '\0' : escape_char;
gchar *new_chars = filename_complete(strings[0], complete);
if (new_chars)
@@ -574,7 +589,8 @@ Cmdline::fnmacro(const gchar *name)
}
static gchar *
-filename_complete(const gchar *filename, gchar completed)
+filename_complete(const gchar *filename, gchar completed,
+ GFileTest file_test)
{
gsize dirname_len;
gchar *dirname, dir_sep;
@@ -635,12 +651,20 @@ filename_complete(const gchar *filename, gchar completed)
* so g_strconcat() works here.
*/
cur_filename = g_strconcat(dirname, cur_basename, NIL);
- if (!*basename && !file_is_visible(cur_filename)) {
+
+ /*
+ * NOTE: This avoids g_file_test() for G_FILE_TEST_EXISTS
+ * since the file we process here should always exist.
+ */
+ if ((!*basename && !file_is_visible(cur_filename)) ||
+ (file_test != G_FILE_TEST_EXISTS &&
+ !g_file_test(cur_filename, file_test))) {
g_free(cur_filename);
continue;
}
- if (g_file_test(cur_filename, G_FILE_TEST_IS_DIR))
+ if (file_test == G_FILE_TEST_IS_DIR ||
+ g_file_test(cur_filename, G_FILE_TEST_IS_DIR))
String::append(cur_filename, dir_sep);
files = g_slist_prepend(files, cur_filename);
@@ -683,7 +707,13 @@ filename_complete(const gchar *filename, gchar completed)
}
interface.popup_show();
- } else if (files_len == 1 && !filename_is_dir((gchar *)files->data)) {
+ } else if (completed && files_len == 1 &&
+ !filename_is_dir((gchar *)files->data)) {
+ /*
+ * FIXME: If we are completing only directories,
+ * we can theoretically insert the completed character
+ * after directories without subdirectories
+ */
String::append(insert, completed);
}
diff --git a/src/ioview.cpp b/src/ioview.cpp
index 72eb94b..323377f 100644
--- a/src/ioview.cpp
+++ b/src/ioview.cpp
@@ -737,7 +737,7 @@ get_absolute_path(const gchar *path)
}
/*
- * There's no platform-independant way to determine if a file
+ * There's no platform-independent way to determine if a file
* is visible/hidden, so we just assume that all files are
* visible.
*/
diff --git a/src/ioview.h b/src/ioview.h
index d314138..d9d8265 100644
--- a/src/ioview.h
+++ b/src/ioview.h
@@ -22,6 +22,7 @@
#include <glib.h>
#include <glib/gstdio.h>
+#include <glib/gprintf.h>
#include "sciteco.h"
#include "interface.h"
@@ -43,6 +44,28 @@ namespace SciTECO {
*/
gchar *get_absolute_path(const gchar *path);
+/**
+ * Normalize path or file name.
+ *
+ * This changes the directory separators
+ * to forward slash (on platforms that support
+ * different directory separator styles).
+ *
+ * @param path The path to normalize.
+ * It is changed in place.
+ * @return Returns `path`. The return value
+ * may be ignored.
+ */
+static inline gchar *
+normalize_path(gchar *path)
+{
+#if G_DIR_SEPARATOR != '/'
+ return g_strdelimit(path, G_DIR_SEPARATOR_S, '/');
+#else
+ return path;
+#endif
+}
+
bool file_is_visible(const gchar *path);
/**
diff --git a/src/main.cpp b/src/main.cpp
index 7ad4d80..6966d22 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -121,7 +121,7 @@ get_default_config_path(const gchar *program)
static inline gchar *
get_default_config_path(const gchar *program)
{
- return g_strdup(g_getenv("HOME") ? : g_get_home_dir());
+ return g_strdup(g_getenv("HOME"));
}
#else
@@ -199,6 +199,18 @@ initialize_environment(const gchar *program)
gchar **env;
/*
+ * Initialize and canonicalize $HOME.
+ * Therefore we can refer to $HOME as the
+ * current user's home directory on any platform
+ * and it can be re-configured even though g_get_home_dir()
+ * evaluates $HOME only beginning with glib v2.36.
+ */
+ g_setenv("HOME", g_get_home_dir(), FALSE);
+ abs_path = get_absolute_path(g_getenv("HOME"));
+ g_setenv("HOME", abs_path, TRUE);
+ g_free(abs_path);
+
+ /*
* Initialize $SCITECOCONFIG and $SCITECOPATH
*/
default_configpath = get_default_config_path(program);
@@ -340,6 +352,8 @@ main(int argc, char **argv)
QRegisters::globals.insert("-");
/* current buffer name and number ("*") */
QRegisters::globals.insert(new QRegisterBufferInfo());
+ /* current working directory ("$") */
+ QRegisters::globals.insert(new QRegisterWorkingDir());
/* environment defaults and registers */
initialize_environment(argv[0]);
diff --git a/src/parser.cpp b/src/parser.cpp
index 5e8f6f6..6462933 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -53,6 +53,7 @@ namespace States {
StateControl control;
StateASCII ascii;
StateFCommand fcommand;
+ StateChangeDir changedir;
StateCondCommand condcommand;
StateECommand ecommand;
StateScintilla_symbols scintilla_symbols;
@@ -1448,6 +1449,7 @@ StateFCommand::StateFCommand() : State()
transitions['D'] = &States::searchdelete;
transitions['S'] = &States::replace;
transitions['R'] = &States::replacedefault;
+ transitions['G'] = &States::changedir;
}
State *
@@ -1552,6 +1554,60 @@ StateFCommand::custom(gchar chr)
return &States::start;
}
+void
+UndoTokenChangeDir::run(void)
+{
+ /*
+ * Changing the directory on rub-out may fail.
+ * This is handled silently.
+ */
+ g_chdir(dir);
+}
+
+/*$
+ * FG[directory]$ -- Change working directory
+ *
+ * Changes the process' current working directory
+ * to <directory> which affects all subsequent
+ * operations on relative file names like
+ * tab-completions.
+ * It is also inherited by external processes spawned
+ * via \fBEC\fP and \fBEG\fP.
+ *
+ * If <directory> is omitted, the working directory
+ * is changed to the current user's home directory
+ * 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.
+ *
+ * The current working directory is also mapped to
+ * the Q-Register \(lq$\(rq (dollar sign) which
+ * may be used retrieve the current working directory.
+ *
+ * String-building characters are enabled on this
+ * command and directories can be tab-completed.
+ */
+State *
+StateChangeDir::done(const gchar *str)
+{
+ 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 (g_chdir(str))
+ /* FIXME: Is errno usable on Windows here? */
+ throw Error("Cannot change working directory "
+ "to \"%s\"", str);
+
+ return &States::start;
+}
+
StateCondCommand::StateCondCommand() : State()
{
transitions['\0'] = this;
diff --git a/src/parser.h b/src/parser.h
index a6a18bb..605c1a5 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -18,6 +18,8 @@
#ifndef __PARSER_H
#define __PARSER_H
+#include <string.h>
+
#include <glib.h>
#include "sciteco.h"
@@ -160,6 +162,12 @@ public:
: StateExpectString(_building, _last) {}
};
+class StateExpectDir : public StateExpectFile {
+public:
+ StateExpectDir(bool _building = true, bool _last = true)
+ : StateExpectFile(_building, _last) {}
+};
+
class StateStart : public State {
public:
StateStart();
@@ -200,6 +208,37 @@ private:
State *custom(gchar chr);
};
+class UndoTokenChangeDir : public UndoToken {
+ gchar *dir;
+
+public:
+ /**
+ * Construct undo token.
+ *
+ * This passes ownership of the directory string
+ * to the undo token object.
+ */
+ UndoTokenChangeDir(gchar *_dir) : dir(_dir) {}
+ ~UndoTokenChangeDir()
+ {
+ g_free(dir);
+ }
+
+ void run(void);
+
+ gsize
+ get_size(void) const
+ {
+ return dir ? sizeof(*this) + strlen(dir)
+ : sizeof(*this);
+ }
+};
+
+class StateChangeDir : public StateExpectDir {
+private:
+ State *done(const gchar *str);
+};
+
class StateCondCommand : public State {
public:
StateCondCommand();
@@ -253,6 +292,7 @@ namespace States {
extern StateControl control;
extern StateASCII ascii;
extern StateFCommand fcommand;
+ extern StateChangeDir changedir;
extern StateCondCommand condcommand;
extern StateECommand ecommand;
extern StateScintilla_symbols scintilla_symbols;
@@ -280,6 +320,12 @@ namespace States {
{
return dynamic_cast<StateExpectFile *>(current);
}
+
+ static inline bool
+ is_dir()
+ {
+ return dynamic_cast<StateExpectDir *>(current);
+ }
}
extern enum Mode {
diff --git a/src/qregisters.cpp b/src/qregisters.cpp
index e792fc0..d924b25 100644
--- a/src/qregisters.cpp
+++ b/src/qregisters.cpp
@@ -36,6 +36,7 @@
#include "expressions.h"
#include "document.h"
#include "ring.h"
+#include "ioview.h"
#include "error.h"
#include "qregisters.h"
@@ -204,6 +205,15 @@ QRegisterData::get_character(gint position)
}
void
+QRegisterData::undo_exchange_string(QRegisterData &reg)
+{
+ if (must_undo)
+ string.undo_exchange();
+ if (reg.must_undo)
+ reg.string.undo_exchange();
+}
+
+void
QRegister::edit(void)
{
if (QRegisters::current)
@@ -368,11 +378,7 @@ QRegisterBufferInfo::get_string(void)
* This does not change the size of the string, so
* get_string_size() still works.
*/
-#if G_DIR_SEPARATOR != '/'
- g_strdelimit(str, G_DIR_SEPARATOR_S, '/');
-#endif
-
- return str;
+ return normalize_path(str);
}
gsize
@@ -407,6 +413,102 @@ QRegisterBufferInfo::edit(void)
QRegisters::view.undo_ssm(SCI_UNDO);
}
+void
+QRegisterWorkingDir::set_string(const gchar *str, gsize len)
+{
+ /* str is not null-terminated */
+ gchar *dir = g_strndup(str, len);
+ int ret = g_chdir(dir);
+
+ g_free(dir);
+
+ if (ret)
+ /* FIXME: Is errno usable on Windows here? */
+ throw Error("Cannot change working directory "
+ "to \"%.*s\"", len, str);
+}
+
+void
+QRegisterWorkingDir::undo_set_string(void)
+{
+ /* passes ownership of string to undo token object */
+ undo.push(new UndoTokenChangeDir(g_get_current_dir()));
+}
+
+gchar *
+QRegisterWorkingDir::get_string(void)
+{
+ /*
+ * On platforms with a default non-forward-slash directory
+ * separator (i.e. Windows), Buffer::filename will have
+ * the wrong separator.
+ * To make the life of macros that evaluate "$" easier,
+ * the directory separators are normalized to "/" here.
+ * This does not change the size of the string, so
+ * get_string_size() still works.
+ */
+ return normalize_path(g_get_current_dir());
+}
+
+gsize
+QRegisterWorkingDir::get_string_size(void)
+{
+ gchar *str = g_get_current_dir();
+ gsize len = strlen(str);
+
+ g_free(str);
+ return len;
+}
+
+gint
+QRegisterWorkingDir::get_character(gint position)
+{
+ gchar *str = QRegisterWorkingDir::get_string();
+ gint ret = -1;
+
+ if (position >= 0 &&
+ position < (gint)strlen(str))
+ ret = str[position];
+
+ g_free(str);
+ return ret;
+}
+
+void
+QRegisterWorkingDir::edit(void)
+{
+ gchar *str;
+
+ QRegister::edit();
+
+ QRegisters::view.ssm(SCI_BEGINUNDOACTION);
+ str = QRegisterWorkingDir::get_string();
+ QRegisters::view.ssm(SCI_SETTEXT, 0, (sptr_t)str);
+ g_free(str);
+ QRegisters::view.ssm(SCI_ENDUNDOACTION);
+
+ QRegisters::view.undo_ssm(SCI_UNDO);
+}
+
+void
+QRegisterWorkingDir::exchange_string(QRegisterData &reg)
+{
+ gchar *own_str = QRegisterWorkingDir::get_string();
+ gchar *other_str = reg.get_string();
+
+ QRegisterData::set_string(other_str);
+ g_free(other_str);
+ reg.set_string(own_str);
+ g_free(own_str);
+}
+
+void
+QRegisterWorkingDir::undo_exchange_string(QRegisterData &reg)
+{
+ QRegisterWorkingDir::undo_set_string();
+ reg.undo_set_string();
+}
+
QRegisterTable::QRegisterTable(bool _undo) : RBTree(), must_undo(_undo)
{
/* general purpose registers */
@@ -495,10 +597,8 @@ QRegisterStack::pop(QRegister &reg)
reg.set_integer(entry->get_integer());
/* exchange document ownership between Stack entry and Q-Register */
- if (reg.must_undo)
- reg.string.undo_exchange();
- entry->string.undo_exchange();
- entry->string.exchange(reg.string);
+ reg.undo_exchange_string(*entry);
+ reg.exchange_string(*entry);
SLIST_REMOVE_HEAD(&head, entries);
/* pass entry ownership to undo stack */
diff --git a/src/qregisters.h b/src/qregisters.h
index b2d856d..1ef0564 100644
--- a/src/qregisters.h
+++ b/src/qregisters.h
@@ -119,10 +119,16 @@ public:
virtual gsize get_string_size(void);
virtual gint get_character(gint position);
+ virtual void
+ exchange_string(QRegisterData &reg)
+ {
+ string.exchange(reg.string);
+ }
+ virtual void undo_exchange_string(QRegisterData &reg);
+
/*
- * The QRegisterStack must currently access the
- * integer and string fields directly to exchange
- * data efficiently.
+ * The QRegisterStack must currently still access the
+ * string fields directly to exchange data efficiently.
*/
friend class QRegisterStack;
};
@@ -183,6 +189,25 @@ public:
void edit(void);
};
+class QRegisterWorkingDir : public QRegister {
+public:
+ QRegisterWorkingDir() : QRegister("$") {}
+
+ void set_string(const gchar *str, gsize len);
+ void undo_set_string(void);
+ void append_string(const gchar *str, gsize len) {}
+ void undo_append_string(void) {}
+
+ gchar *get_string(void);
+ gsize get_string_size(void);
+ gint get_character(gint pos);
+
+ void edit(void);
+
+ void exchange_string(QRegisterData &reg);
+ void undo_exchange_string(QRegisterData &reg);
+};
+
class QRegisterTable : private RBTree {
class UndoTokenRemove : public UndoTokenWithSize<UndoTokenRemove> {
QRegisterTable *table;
diff --git a/src/spawn.cpp b/src/spawn.cpp
index c1ffce8..9f951f5 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -177,7 +177,7 @@ parse_shell_command_line(const gchar *cmdline, GError **error)
* This feature may be used to take action depending on a
* specific process exit code.
*
- * <command> execution is by default platform-dependant.
+ * <command> execution is by default platform-dependent.
* On Windows, <command> is passed to the default command
* interpreter \(lqcmd.exe\(rq with the \(lq/c\(rq
* command-line argument.
@@ -186,7 +186,7 @@ parse_shell_command_line(const gchar *cmdline, GError **error)
* Therefore operating system restrictions on the maximum
* length of command-line arguments apply to <command> and
* quoting of parameters within <command> is somewhat platform
- * dependant.
+ * dependent.
* On all other platforms, \*(ST will uniformly parse
* <command> just as an UNIX98 \(lq/bin/sh\(rq would, but without
* performing any expansions.