diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2015-06-02 15:38:00 +0200 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2015-06-02 15:38:00 +0200 |
commit | 02d414b3ab447e637fef776e387aa5a07293738a (patch) | |
tree | 60d5db01e9e0cc15f1f8e84abb14138cb0ca54a3 | |
parent | 6c62ccc436d972a872616c187d460ed61c8bc0d2 (diff) | |
download | sciteco-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.in | 25 | ||||
-rw-r--r-- | doc/sciteco.7.template | 27 | ||||
-rw-r--r-- | src/cmdline.cpp | 42 | ||||
-rw-r--r-- | src/ioview.cpp | 2 | ||||
-rw-r--r-- | src/ioview.h | 23 | ||||
-rw-r--r-- | src/main.cpp | 16 | ||||
-rw-r--r-- | src/parser.cpp | 56 | ||||
-rw-r--r-- | src/parser.h | 46 | ||||
-rw-r--r-- | src/qregisters.cpp | 118 | ||||
-rw-r--r-- | src/qregisters.h | 31 | ||||
-rw-r--r-- | src/spawn.cpp | 4 |
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 ®) +{ + 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 ®) +{ + 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 ®) +{ + 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.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 ®) + { + string.exchange(reg.string); + } + virtual void undo_exchange_string(QRegisterData ®); + /* - * 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 ®); + void undo_exchange_string(QRegisterData ®); +}; + 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. |