mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-12 04:58:57 +00:00
Apply new indentation, brace, and whitespace style
This commit is contained in:
parent
bab69f2672
commit
9992b8eb0e
103 changed files with 32091 additions and 30772 deletions
89
autoload.cpp
89
autoload.cpp
|
@ -17,17 +17,24 @@ The classes responsible for autoloading functions and completions.
|
|||
/* The time before we'll recheck an autoloaded file */
|
||||
static const int kAutoloadStalenessInterval = 15;
|
||||
|
||||
file_access_attempt_t access_file(const wcstring &path, int mode) {
|
||||
file_access_attempt_t access_file(const wcstring &path, int mode)
|
||||
{
|
||||
//printf("Touch %ls\n", path.c_str());
|
||||
file_access_attempt_t result = {0};
|
||||
struct stat statbuf;
|
||||
if (wstat(path, &statbuf)) {
|
||||
if (wstat(path, &statbuf))
|
||||
{
|
||||
result.error = errno;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
result.mod_time = statbuf.st_mtime;
|
||||
if (waccess(path, mode)) {
|
||||
if (waccess(path, mode))
|
||||
{
|
||||
result.error = errno;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
result.accessible = true;
|
||||
}
|
||||
}
|
||||
|
@ -49,11 +56,13 @@ autoload_t::autoload_t(const wcstring &env_var_name_var, const builtin_script_t
|
|||
pthread_mutex_init(&lock, NULL);
|
||||
}
|
||||
|
||||
autoload_t::~autoload_t() {
|
||||
autoload_t::~autoload_t()
|
||||
{
|
||||
pthread_mutex_destroy(&lock);
|
||||
}
|
||||
|
||||
void autoload_t::node_was_evicted(autoload_function_t *node) {
|
||||
void autoload_t::node_was_evicted(autoload_function_t *node)
|
||||
{
|
||||
// This should only ever happen on the main thread
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
|
||||
|
@ -133,7 +142,8 @@ static bool script_name_precedes_script_name(const builtin_script_t &script1, co
|
|||
return wcscmp(script1.name, script2.name) < 0;
|
||||
}
|
||||
|
||||
void autoload_t::unload_all(void) {
|
||||
void autoload_t::unload_all(void)
|
||||
{
|
||||
scoped_lock locker(lock);
|
||||
this->evict_all_nodes();
|
||||
}
|
||||
|
@ -146,7 +156,8 @@ bool autoload_t::has_tried_loading( const wcstring &cmd )
|
|||
return func != NULL;
|
||||
}
|
||||
|
||||
static bool is_stale(const autoload_function_t *func) {
|
||||
static bool is_stale(const autoload_function_t *func)
|
||||
{
|
||||
/** Return whether this function is stale. Internalized functions can never be stale. */
|
||||
return ! func->is_internalized && time(NULL) - func->access.last_checked > kAutoloadStalenessInterval;
|
||||
}
|
||||
|
@ -155,11 +166,15 @@ autoload_function_t *autoload_t::get_autoloaded_function_with_creation(const wcs
|
|||
{
|
||||
ASSERT_IS_LOCKED(lock);
|
||||
autoload_function_t *func = this->get_node(cmd);
|
||||
if (! func) {
|
||||
if (! func)
|
||||
{
|
||||
func = new autoload_function_t(cmd);
|
||||
if (allow_eviction) {
|
||||
if (allow_eviction)
|
||||
{
|
||||
this->add_node(func);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
this->add_node_without_eviction(func);
|
||||
}
|
||||
}
|
||||
|
@ -196,22 +211,30 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
|||
|
||||
/* Determine if we can use this cached function */
|
||||
bool use_cached;
|
||||
if (! func) {
|
||||
if (! func)
|
||||
{
|
||||
/* Can't use a function that doesn't exist */
|
||||
use_cached = false;
|
||||
} else if (really_load && ! func->is_placeholder && ! func->is_loaded) {
|
||||
}
|
||||
else if (really_load && ! func->is_placeholder && ! func->is_loaded)
|
||||
{
|
||||
/* Can't use an unloaded function */
|
||||
use_cached = false;
|
||||
} else if ( ! allow_stale_functions && is_stale(func)) {
|
||||
}
|
||||
else if (! allow_stale_functions && is_stale(func))
|
||||
{
|
||||
/* Can't use a stale function */
|
||||
use_cached = false;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* I guess we can use it */
|
||||
use_cached = true;
|
||||
}
|
||||
|
||||
/* If we can use this function, return whether we were able to access it */
|
||||
if (use_cached) {
|
||||
if (use_cached)
|
||||
{
|
||||
return func->is_internalized || func->access.accessible;
|
||||
}
|
||||
}
|
||||
|
@ -235,7 +258,8 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
|||
matching_builtin_script = found;
|
||||
}
|
||||
}
|
||||
if (matching_builtin_script) {
|
||||
if (matching_builtin_script)
|
||||
{
|
||||
has_script_source = true;
|
||||
script_source = str2wcstring(matching_builtin_script->def);
|
||||
|
||||
|
@ -259,7 +283,8 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
|||
wcstring path = next + L"/" + cmd + L".fish";
|
||||
|
||||
const file_access_attempt_t access = access_file(path, R_OK);
|
||||
if (access.accessible) {
|
||||
if (access.accessible)
|
||||
{
|
||||
/* Found it! */
|
||||
found_file = true;
|
||||
|
||||
|
@ -269,7 +294,8 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
|||
|
||||
/* Generate the source if we need to load it */
|
||||
bool need_to_load_function = really_load && (func == NULL || func->access.mod_time != access.mod_time || ! func->is_loaded);
|
||||
if (need_to_load_function) {
|
||||
if (need_to_load_function)
|
||||
{
|
||||
|
||||
/* Generate the script source */
|
||||
wcstring esc = escape_string(path, 1);
|
||||
|
@ -277,7 +303,8 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
|||
has_script_source = true;
|
||||
|
||||
/* Remove any loaded command because we are going to reload it. Note that this will deadlock if command_removed calls back into us. */
|
||||
if (func && func->is_loaded) {
|
||||
if (func && func->is_loaded)
|
||||
{
|
||||
command_removed(cmd);
|
||||
func->is_placeholder = false;
|
||||
}
|
||||
|
@ -287,7 +314,8 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
|||
}
|
||||
|
||||
/* Create the function if we haven't yet. This does not load it. Do not trigger eviction unless we are actually loading, because we don't want to evict off of the main thread. */
|
||||
if (! func) {
|
||||
if (! func)
|
||||
{
|
||||
func = get_autoloaded_function_with_creation(cmd, really_load);
|
||||
}
|
||||
|
||||
|
@ -311,12 +339,16 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
|||
scoped_lock locker(lock);
|
||||
/* Generate a placeholder */
|
||||
autoload_function_t *func = this->get_node(cmd);
|
||||
if (! func) {
|
||||
if (! func)
|
||||
{
|
||||
func = new autoload_function_t(cmd);
|
||||
func->is_placeholder = true;
|
||||
if (really_load) {
|
||||
if (really_load)
|
||||
{
|
||||
this->add_node(func);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
this->add_node_without_eviction(func);
|
||||
}
|
||||
}
|
||||
|
@ -336,9 +368,12 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
|||
|
||||
}
|
||||
|
||||
if (really_load) {
|
||||
if (really_load)
|
||||
{
|
||||
return reloaded;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return found_file || has_script_source;
|
||||
}
|
||||
}
|
||||
|
|
12
autoload.h
12
autoload.h
|
@ -14,7 +14,8 @@
|
|||
#include "lru.h"
|
||||
|
||||
/** A struct responsible for recording an attempt to access a file. */
|
||||
struct file_access_attempt_t {
|
||||
struct file_access_attempt_t
|
||||
{
|
||||
time_t mod_time; /** The modification time of the file */
|
||||
time_t last_checked; /** When we last checked the file */
|
||||
bool accessible; /** Whether we believe we could access this file */
|
||||
|
@ -40,7 +41,8 @@ class env_vars_snapshot_t;
|
|||
/**
|
||||
A class that represents a path from which we can autoload, and the autoloaded contents.
|
||||
*/
|
||||
class autoload_t : private lru_cache_t<autoload_function_t> {
|
||||
class autoload_t : private lru_cache_t<autoload_function_t>
|
||||
{
|
||||
private:
|
||||
|
||||
/** Lock for thread safety */
|
||||
|
@ -64,11 +66,13 @@ private:
|
|||
*/
|
||||
std::set<wcstring> is_loading_set;
|
||||
|
||||
bool is_loading(const wcstring &name) const {
|
||||
bool is_loading(const wcstring &name) const
|
||||
{
|
||||
return is_loading_set.find(name) != is_loading_set.end();
|
||||
}
|
||||
|
||||
void remove_all_functions(void) {
|
||||
void remove_all_functions(void)
|
||||
{
|
||||
this->evict_all_nodes();
|
||||
}
|
||||
|
||||
|
|
173
builtin.cpp
173
builtin.cpp
|
@ -119,17 +119,20 @@ int builtin_err_redirect;
|
|||
/* Buffers for storing the output of builtin functions */
|
||||
wcstring stdout_buffer, stderr_buffer;
|
||||
|
||||
const wcstring &get_stdout_buffer() {
|
||||
const wcstring &get_stdout_buffer()
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
return stdout_buffer;
|
||||
}
|
||||
|
||||
const wcstring &get_stderr_buffer() {
|
||||
const wcstring &get_stderr_buffer()
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
return stderr_buffer;
|
||||
}
|
||||
|
||||
void builtin_show_error(const wcstring &err) {
|
||||
void builtin_show_error(const wcstring &err)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
stderr_buffer.append(err);
|
||||
}
|
||||
|
@ -137,7 +140,8 @@ void builtin_show_error(const wcstring &err) {
|
|||
/**
|
||||
Stack containing builtin I/O for recursive builtin calls.
|
||||
*/
|
||||
struct io_stack_elem_t {
|
||||
struct io_stack_elem_t
|
||||
{
|
||||
int in;
|
||||
wcstring out;
|
||||
wcstring err;
|
||||
|
@ -1103,7 +1107,8 @@ static void functions_def( const wcstring &name, wcstring &out )
|
|||
/* Typically we prefer to specify the function name first, e.g. "function foo --description bar"
|
||||
But If the function name starts with a -, we'll need to output it after all the options. */
|
||||
bool defer_function_name = (name.at(0) == L'-');
|
||||
if ( ! defer_function_name ){
|
||||
if (! defer_function_name)
|
||||
{
|
||||
out.append(name);
|
||||
}
|
||||
|
||||
|
@ -1175,7 +1180,8 @@ static void functions_def( const wcstring &name, wcstring &out )
|
|||
}
|
||||
|
||||
/* Output the function name if we deferred it */
|
||||
if ( defer_function_name ){
|
||||
if (defer_function_name)
|
||||
{
|
||||
out.append(L" -- ");
|
||||
out.append(name);
|
||||
}
|
||||
|
@ -1184,7 +1190,8 @@ static void functions_def( const wcstring &name, wcstring &out )
|
|||
append_format(out, L"\n\t%ls", def.c_str());
|
||||
|
||||
/* Append a newline before the 'end', unless there already is one there */
|
||||
if (! string_suffixes_string(L"\n", def)) {
|
||||
if (! string_suffixes_string(L"\n", def))
|
||||
{
|
||||
out.push_back(L'\n');
|
||||
}
|
||||
out.append(L"end\n");
|
||||
|
@ -1469,26 +1476,48 @@ static unsigned int builtin_echo_digit(wchar_t wc, unsigned int base)
|
|||
assert(base == 8 || base == 16);
|
||||
switch (wc)
|
||||
{
|
||||
case L'0': return 0;
|
||||
case L'1': return 1;
|
||||
case L'2': return 2;
|
||||
case L'3': return 3;
|
||||
case L'4': return 4;
|
||||
case L'5': return 5;
|
||||
case L'6': return 6;
|
||||
case L'7': return 7;
|
||||
case L'0':
|
||||
return 0;
|
||||
case L'1':
|
||||
return 1;
|
||||
case L'2':
|
||||
return 2;
|
||||
case L'3':
|
||||
return 3;
|
||||
case L'4':
|
||||
return 4;
|
||||
case L'5':
|
||||
return 5;
|
||||
case L'6':
|
||||
return 6;
|
||||
case L'7':
|
||||
return 7;
|
||||
}
|
||||
|
||||
if (base == 16) switch (wc)
|
||||
{
|
||||
case L'8': return 8;
|
||||
case L'9': return 9;
|
||||
case L'a': case L'A': return 10;
|
||||
case L'b': case L'B': return 11;
|
||||
case L'c': case L'C': return 12;
|
||||
case L'd': case L'D': return 13;
|
||||
case L'e': case L'E': return 14;
|
||||
case L'f': case L'F': return 15;
|
||||
case L'8':
|
||||
return 8;
|
||||
case L'9':
|
||||
return 9;
|
||||
case L'a':
|
||||
case L'A':
|
||||
return 10;
|
||||
case L'b':
|
||||
case L'B':
|
||||
return 11;
|
||||
case L'c':
|
||||
case L'C':
|
||||
return 12;
|
||||
case L'd':
|
||||
case L'D':
|
||||
return 13;
|
||||
case L'e':
|
||||
case L'E':
|
||||
return 14;
|
||||
case L'f':
|
||||
case L'F':
|
||||
return 15;
|
||||
}
|
||||
return UINT_MAX;
|
||||
}
|
||||
|
@ -1561,16 +1590,26 @@ static int builtin_echo( parser_t &parser, wchar_t **argv )
|
|||
|
||||
/* Process options */
|
||||
bool print_newline = true, print_spaces = true, interpret_special_chars = false;
|
||||
while (*argv) {
|
||||
if (! wcscmp(*argv, L"-n")) {
|
||||
while (*argv)
|
||||
{
|
||||
if (! wcscmp(*argv, L"-n"))
|
||||
{
|
||||
print_newline = false;
|
||||
} else if (! wcscmp(*argv, L"-s")) {
|
||||
}
|
||||
else if (! wcscmp(*argv, L"-s"))
|
||||
{
|
||||
print_spaces = false;
|
||||
} else if (! wcscmp(*argv, L"-e")) {
|
||||
}
|
||||
else if (! wcscmp(*argv, L"-e"))
|
||||
{
|
||||
interpret_special_chars = true;
|
||||
} else if (! wcscmp(*argv, L"-E")) {
|
||||
}
|
||||
else if (! wcscmp(*argv, L"-E"))
|
||||
{
|
||||
interpret_special_chars = false;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
argv++;
|
||||
|
@ -1579,7 +1618,8 @@ static int builtin_echo( parser_t &parser, wchar_t **argv )
|
|||
/* The special character \c can be used to indicate no more output */
|
||||
bool continue_output = true;
|
||||
|
||||
for (size_t idx = 0; continue_output && argv[idx] != NULL; idx++) {
|
||||
for (size_t idx = 0; continue_output && argv[idx] != NULL; idx++)
|
||||
{
|
||||
|
||||
if (print_spaces && idx > 0)
|
||||
stdout_buffer.push_back(' ');
|
||||
|
@ -1599,17 +1639,38 @@ static int builtin_echo( parser_t &parser, wchar_t **argv )
|
|||
size_t consumed = 1;
|
||||
switch (str[j+1])
|
||||
{
|
||||
case L'a': wc = L'\a'; break;
|
||||
case L'b': wc = L'\b'; break;
|
||||
case L'e': wc = L'\e'; break;
|
||||
case L'f': wc = L'\f'; break;
|
||||
case L'n': wc = L'\n'; break;
|
||||
case L'r': wc = L'\r'; break;
|
||||
case L't': wc = L'\t'; break;
|
||||
case L'v': wc = L'\v'; break;
|
||||
case L'\\': wc = L'\\'; break;
|
||||
case L'a':
|
||||
wc = L'\a';
|
||||
break;
|
||||
case L'b':
|
||||
wc = L'\b';
|
||||
break;
|
||||
case L'e':
|
||||
wc = L'\e';
|
||||
break;
|
||||
case L'f':
|
||||
wc = L'\f';
|
||||
break;
|
||||
case L'n':
|
||||
wc = L'\n';
|
||||
break;
|
||||
case L'r':
|
||||
wc = L'\r';
|
||||
break;
|
||||
case L't':
|
||||
wc = L'\t';
|
||||
break;
|
||||
case L'v':
|
||||
wc = L'\v';
|
||||
break;
|
||||
case L'\\':
|
||||
wc = L'\\';
|
||||
break;
|
||||
|
||||
case L'c': wc = 0; continue_output = false; break;
|
||||
case L'c':
|
||||
wc = 0;
|
||||
continue_output = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
|
@ -1648,9 +1709,12 @@ static int builtin_pwd( parser_t &parser, wchar_t **argv )
|
|||
{
|
||||
wchar_t dir_path[4096];
|
||||
wchar_t *res = wgetcwd(dir_path, 4096);
|
||||
if (res == NULL) {
|
||||
if (res == NULL)
|
||||
{
|
||||
return STATUS_BUILTIN_ERROR;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
stdout_buffer.append(dir_path);
|
||||
stdout_buffer.push_back(L'\n');
|
||||
return STATUS_BUILTIN_OK;
|
||||
|
@ -2749,7 +2813,8 @@ static int builtin_cd( parser_t &parser, wchar_t **argv )
|
|||
argv[0]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
dir_in = argv[1];
|
||||
}
|
||||
|
||||
|
@ -3967,12 +4032,16 @@ static const builtin_data_t builtin_datas[]=
|
|||
|
||||
#define BUILTIN_COUNT (sizeof builtin_datas / sizeof *builtin_datas)
|
||||
|
||||
static const builtin_data_t *builtin_lookup(const wcstring &name) {
|
||||
static const builtin_data_t *builtin_lookup(const wcstring &name)
|
||||
{
|
||||
const builtin_data_t *array_end = builtin_datas + BUILTIN_COUNT;
|
||||
const builtin_data_t *found = std::lower_bound(builtin_datas, array_end, name);
|
||||
if (found != array_end && name == found->name) {
|
||||
if (found != array_end && name == found->name)
|
||||
{
|
||||
return found;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
@ -4048,14 +4117,17 @@ wcstring_list_t builtin_get_names(void)
|
|||
{
|
||||
wcstring_list_t result;
|
||||
result.reserve(BUILTIN_COUNT);
|
||||
for (size_t i=0; i < BUILTIN_COUNT; i++) {
|
||||
for (size_t i=0; i < BUILTIN_COUNT; i++)
|
||||
{
|
||||
result.push_back(builtin_datas[i].name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void builtin_get_names(std::vector<completion_t> &list) {
|
||||
for (size_t i=0; i < BUILTIN_COUNT; i++) {
|
||||
void builtin_get_names(std::vector<completion_t> &list)
|
||||
{
|
||||
for (size_t i=0; i < BUILTIN_COUNT; i++)
|
||||
{
|
||||
list.push_back(completion_t(builtin_datas[i].name));
|
||||
}
|
||||
}
|
||||
|
@ -4064,7 +4136,8 @@ wcstring builtin_get_desc( const wcstring &name )
|
|||
{
|
||||
wcstring result;
|
||||
const builtin_data_t *builtin = builtin_lookup(name);
|
||||
if (builtin) {
|
||||
if (builtin)
|
||||
{
|
||||
result = _(builtin->desc);
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -239,7 +239,8 @@ static int parse_index( std::vector<long> &indexes,
|
|||
}
|
||||
|
||||
src = end;
|
||||
if ( *src==L'.' && *(src+1)==L'.' ){
|
||||
if (*src==L'.' && *(src+1)==L'.')
|
||||
{
|
||||
src+=2;
|
||||
long l_ind2 = wcstol(src, &end, 10);
|
||||
if (end==src || errno)
|
||||
|
@ -253,13 +254,15 @@ static int parse_index( std::vector<long> &indexes,
|
|||
l_ind2 = var_count+l_ind2+1;
|
||||
}
|
||||
int direction = l_ind2<l_ind ? -1 : 1 ;
|
||||
for (long jjj = l_ind; jjj*direction <= l_ind2*direction; jjj+=direction) {
|
||||
for (long jjj = l_ind; jjj*direction <= l_ind2*direction; jjj+=direction)
|
||||
{
|
||||
// debug(0, L"Expand range [set]: %i\n", jjj);
|
||||
indexes.push_back(jjj);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
indexes.push_back(l_ind);
|
||||
count++;
|
||||
}
|
||||
|
@ -311,9 +314,11 @@ static void erase_values(wcstring_list_t &list, const std::vector<long> &indexes
|
|||
|
||||
// Now walk the set backwards, so we encounter larger indexes first, and remove elements at the given (1-based) indexes.
|
||||
std::set<long>::const_reverse_iterator iter;
|
||||
for (iter = indexes_set.rbegin(); iter != indexes_set.rend(); iter++) {
|
||||
for (iter = indexes_set.rbegin(); iter != indexes_set.rend(); iter++)
|
||||
{
|
||||
long val = *iter;
|
||||
if (val > 0 && (size_t)val <= list.size()) {
|
||||
if (val > 0 && (size_t)val <= list.size())
|
||||
{
|
||||
// One-based indexing!
|
||||
list.erase(list.begin() + val - 1);
|
||||
}
|
||||
|
|
269
builtin_test.cpp
269
builtin_test.cpp
|
@ -14,7 +14,8 @@ Implemented from scratch (yes, really) by way of IEEE 1003.1 as reference.
|
|||
#include <memory>
|
||||
|
||||
|
||||
enum {
|
||||
enum
|
||||
{
|
||||
BUILTIN_TEST_SUCCESS = STATUS_BUILTIN_OK,
|
||||
BUILTIN_TEST_FAIL = STATUS_BUILTIN_ERROR
|
||||
};
|
||||
|
@ -22,14 +23,17 @@ enum {
|
|||
|
||||
int builtin_test(parser_t &parser, wchar_t **argv);
|
||||
|
||||
static const wchar_t * const condstr[] = {
|
||||
static const wchar_t * const condstr[] =
|
||||
{
|
||||
L"!", L"&&", L"||", L"==", L"!=", L"<", L">", L"-nt", L"-ot", L"-ef", L"-eq",
|
||||
L"-ne", L"-lt", L"-gt", L"-le", L"-ge", L"=~"
|
||||
};
|
||||
|
||||
namespace test_expressions {
|
||||
namespace test_expressions
|
||||
{
|
||||
|
||||
enum token_t {
|
||||
enum token_t
|
||||
{
|
||||
test_unknown, // arbitrary string
|
||||
|
||||
test_bang, // "!", inverts sense
|
||||
|
@ -77,12 +81,18 @@ namespace test_expressions {
|
|||
static bool unary_primary_evaluate(test_expressions::token_t token, const wcstring &arg, wcstring_list_t &errors);
|
||||
|
||||
|
||||
enum {
|
||||
enum
|
||||
{
|
||||
UNARY_PRIMARY = 1 << 0,
|
||||
BINARY_PRIMARY = 1 << 1
|
||||
};
|
||||
|
||||
static const struct token_info_t { token_t tok; const wchar_t *string; unsigned int flags; } token_infos[] =
|
||||
static const struct token_info_t
|
||||
{
|
||||
token_t tok;
|
||||
const wchar_t *string;
|
||||
unsigned int flags;
|
||||
} token_infos[] =
|
||||
{
|
||||
{test_unknown, L"", 0},
|
||||
{test_bang, L"!", 0},
|
||||
|
@ -118,9 +128,12 @@ namespace test_expressions {
|
|||
{test_paren_close, L")", 0}
|
||||
};
|
||||
|
||||
const token_info_t *token_for_string(const wcstring &str) {
|
||||
for (size_t i=0; i < sizeof token_infos / sizeof *token_infos; i++) {
|
||||
if (str == token_infos[i].string) {
|
||||
const token_info_t *token_for_string(const wcstring &str)
|
||||
{
|
||||
for (size_t i=0; i < sizeof token_infos / sizeof *token_infos; i++)
|
||||
{
|
||||
if (str == token_infos[i].string)
|
||||
{
|
||||
return &token_infos[i];
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +158,8 @@ namespace test_expressions {
|
|||
*/
|
||||
|
||||
class expression;
|
||||
class test_parser {
|
||||
class test_parser
|
||||
{
|
||||
private:
|
||||
wcstring_list_t strings;
|
||||
wcstring_list_t errors;
|
||||
|
@ -153,7 +167,10 @@ namespace test_expressions {
|
|||
expression *error(const wchar_t *fmt, ...);
|
||||
void add_error(const wchar_t *fmt, ...);
|
||||
|
||||
const wcstring &arg(unsigned int idx) { return strings.at(idx); }
|
||||
const wcstring &arg(unsigned int idx)
|
||||
{
|
||||
return strings.at(idx);
|
||||
}
|
||||
|
||||
public:
|
||||
test_parser(const wcstring_list_t &val) : strings(val)
|
||||
|
@ -172,7 +189,8 @@ namespace test_expressions {
|
|||
static expression *parse_args(const wcstring_list_t &args, wcstring &err);
|
||||
};
|
||||
|
||||
struct range_t {
|
||||
struct range_t
|
||||
{
|
||||
unsigned int start;
|
||||
unsigned int end;
|
||||
|
||||
|
@ -181,7 +199,8 @@ namespace test_expressions {
|
|||
|
||||
|
||||
/* Base class for expressions */
|
||||
class expression {
|
||||
class expression
|
||||
{
|
||||
protected:
|
||||
expression(token_t what, range_t where) : token(what), range(where) { }
|
||||
|
||||
|
@ -198,7 +217,8 @@ namespace test_expressions {
|
|||
typedef std::auto_ptr<expression> expr_ref_t;
|
||||
|
||||
/* Single argument like -n foo or "just a string" */
|
||||
class unary_primary : public expression {
|
||||
class unary_primary : public expression
|
||||
{
|
||||
public:
|
||||
wcstring arg;
|
||||
unary_primary(token_t tok, range_t where, const wcstring &what) : expression(tok, where), arg(what) { }
|
||||
|
@ -206,7 +226,8 @@ namespace test_expressions {
|
|||
};
|
||||
|
||||
/* Two argument primary like foo != bar */
|
||||
class binary_primary : public expression {
|
||||
class binary_primary : public expression
|
||||
{
|
||||
public:
|
||||
wcstring arg_left;
|
||||
wcstring arg_right;
|
||||
|
@ -217,7 +238,8 @@ namespace test_expressions {
|
|||
};
|
||||
|
||||
/* Unary operator like bang */
|
||||
class unary_operator : public expression {
|
||||
class unary_operator : public expression
|
||||
{
|
||||
public:
|
||||
expr_ref_t subject;
|
||||
unary_operator(token_t tok, range_t where, expr_ref_t &exp) : expression(tok, where), subject(exp) { }
|
||||
|
@ -225,7 +247,8 @@ namespace test_expressions {
|
|||
};
|
||||
|
||||
/* Combining expression. Contains a list of AND or OR expressions. It takes more than two so that we don't have to worry about precedence in the parser. */
|
||||
class combining_expression : public expression {
|
||||
class combining_expression : public expression
|
||||
{
|
||||
public:
|
||||
const std::vector<expression *> subjects;
|
||||
const std::vector<token_t> combiners;
|
||||
|
@ -237,8 +260,10 @@ namespace test_expressions {
|
|||
}
|
||||
|
||||
/* We are responsible for destroying our expressions */
|
||||
virtual ~combining_expression() {
|
||||
for (size_t i=0; i < subjects.size(); i++) {
|
||||
virtual ~combining_expression()
|
||||
{
|
||||
for (size_t i=0; i < subjects.size(); i++)
|
||||
{
|
||||
delete subjects[i];
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +272,8 @@ namespace test_expressions {
|
|||
};
|
||||
|
||||
/* Parenthetical expression */
|
||||
class parenthetical_expression : public expression {
|
||||
class parenthetical_expression : public expression
|
||||
{
|
||||
public:
|
||||
expr_ref_t contents;
|
||||
parenthetical_expression(token_t tok, range_t where, expr_ref_t &expr) : expression(tok, where), contents(expr) { }
|
||||
|
@ -255,7 +281,8 @@ namespace test_expressions {
|
|||
virtual bool evaluate(wcstring_list_t &errors);
|
||||
};
|
||||
|
||||
void test_parser::add_error(const wchar_t *fmt, ...) {
|
||||
void test_parser::add_error(const wchar_t *fmt, ...)
|
||||
{
|
||||
assert(fmt != NULL);
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
|
@ -263,7 +290,8 @@ namespace test_expressions {
|
|||
va_end(va);
|
||||
}
|
||||
|
||||
expression *test_parser::error(const wchar_t *fmt, ...) {
|
||||
expression *test_parser::error(const wchar_t *fmt, ...)
|
||||
{
|
||||
assert(fmt != NULL);
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
|
@ -272,25 +300,34 @@ namespace test_expressions {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
expression *test_parser::parse_unary_expression(unsigned int start, unsigned int end) {
|
||||
if (start >= end) {
|
||||
expression *test_parser::parse_unary_expression(unsigned int start, unsigned int end)
|
||||
{
|
||||
if (start >= end)
|
||||
{
|
||||
return error(L"Missing argument at index %u", start);
|
||||
}
|
||||
token_t tok = token_for_string(arg(start))->tok;
|
||||
if (tok == test_bang) {
|
||||
if (tok == test_bang)
|
||||
{
|
||||
expr_ref_t subject(parse_unary_expression(start + 1, end));
|
||||
if (subject.get()) {
|
||||
if (subject.get())
|
||||
{
|
||||
return new unary_operator(tok, range_t(start, subject->range.end), subject);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return parse_primary(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse a combining expression (AND, OR) */
|
||||
expression *test_parser::parse_combining_expression(unsigned int start, unsigned int end) {
|
||||
expression *test_parser::parse_combining_expression(unsigned int start, unsigned int end)
|
||||
{
|
||||
if (start >= end)
|
||||
return NULL;
|
||||
|
||||
|
@ -298,12 +335,15 @@ namespace test_expressions {
|
|||
std::vector<token_t> combiners;
|
||||
unsigned int idx = start;
|
||||
|
||||
while (idx < end) {
|
||||
while (idx < end)
|
||||
{
|
||||
|
||||
if (! subjects.empty()) {
|
||||
if (! subjects.empty())
|
||||
{
|
||||
/* This is not the first expression, so we expect a combiner. */
|
||||
token_t combiner = token_for_string(arg(idx))->tok;
|
||||
if (combiner != test_combine_and && combiner != test_combine_or) {
|
||||
if (combiner != test_combine_and && combiner != test_combine_or)
|
||||
{
|
||||
/* Not a combiner, we're done */
|
||||
break;
|
||||
}
|
||||
|
@ -313,7 +353,8 @@ namespace test_expressions {
|
|||
|
||||
/* Parse another expression */
|
||||
expression *expr = parse_unary_expression(idx, end);
|
||||
if (! expr) {
|
||||
if (! expr)
|
||||
{
|
||||
add_error(L"Missing argument at index %u", idx);
|
||||
break;
|
||||
}
|
||||
|
@ -323,21 +364,27 @@ namespace test_expressions {
|
|||
subjects.push_back(expr);
|
||||
}
|
||||
|
||||
if (! subjects.empty()) {
|
||||
if (! subjects.empty())
|
||||
{
|
||||
/* Our new expression takes ownership of all expressions we created. The token we pass is irrelevant. */
|
||||
return new combining_expression(test_combine_and, range_t(start, idx), subjects, combiners);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No subjects */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
expression *test_parser::parse_unary_primary(unsigned int start, unsigned int end) {
|
||||
expression *test_parser::parse_unary_primary(unsigned int start, unsigned int end)
|
||||
{
|
||||
/* We need two arguments */
|
||||
if (start >= end) {
|
||||
if (start >= end)
|
||||
{
|
||||
return error(L"Missing argument at index %u", start);
|
||||
}
|
||||
if (start + 1 >= end) {
|
||||
if (start + 1 >= end)
|
||||
{
|
||||
return error(L"Missing argument at index %u", start + 1);
|
||||
}
|
||||
|
||||
|
@ -349,19 +396,22 @@ namespace test_expressions {
|
|||
return new unary_primary(info->tok, range_t(start, start + 2), arg(start + 1));
|
||||
}
|
||||
|
||||
expression *test_parser::parse_just_a_string(unsigned int start, unsigned int end) {
|
||||
expression *test_parser::parse_just_a_string(unsigned int start, unsigned int end)
|
||||
{
|
||||
/* Handle a string as a unary primary that is not a token of any other type.
|
||||
e.g. 'test foo -a bar' should evaluate to true
|
||||
We handle this with a unary primary of test_string_n
|
||||
*/
|
||||
|
||||
/* We need one arguments */
|
||||
if (start >= end) {
|
||||
if (start >= end)
|
||||
{
|
||||
return error(L"Missing argument at index %u", start);
|
||||
}
|
||||
|
||||
const token_info_t *info = token_for_string(arg(start));
|
||||
if (info->tok != test_unknown) {
|
||||
if (info->tok != test_unknown)
|
||||
{
|
||||
return error(L"Unexpected argument type at index %u", start);
|
||||
}
|
||||
|
||||
|
@ -370,9 +420,11 @@ namespace test_expressions {
|
|||
}
|
||||
|
||||
#if 0
|
||||
expression *test_parser::parse_unary_primary(unsigned int start, unsigned int end) {
|
||||
expression *test_parser::parse_unary_primary(unsigned int start, unsigned int end)
|
||||
{
|
||||
/* We need either one or two arguments */
|
||||
if (start >= end) {
|
||||
if (start >= end)
|
||||
{
|
||||
return error(L"Missing argument at index %u", start);
|
||||
}
|
||||
|
||||
|
@ -381,23 +433,29 @@ namespace test_expressions {
|
|||
|
||||
/* All our unary primaries are prefix, so any operator is at start. But it also may just be a string, with no operator. */
|
||||
const token_info_t *info = token_for_string(arg(start));
|
||||
if (info->flags & UNARY_PRIMARY) {
|
||||
if (info->flags & UNARY_PRIMARY)
|
||||
{
|
||||
/* We have an operator. Skip the operator argument */
|
||||
arg_idx = start + 1;
|
||||
|
||||
/* We have some freedom here...do we allow other tokens for the argument to operate on?
|
||||
For example, should 'test -n =' work? I say yes. So no typechecking on the next token. */
|
||||
|
||||
} else if (info->tok == test_unknown) {
|
||||
}
|
||||
else if (info->tok == test_unknown)
|
||||
{
|
||||
/* "Just a string. */
|
||||
arg_idx = start;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Here we don't allow arbitrary tokens as "just a string." I.e. 'test = -a =' should have a parse error. We could relax this at some point. */
|
||||
return error(L"Parse error at argument index %u", start);
|
||||
}
|
||||
|
||||
/* Verify we have the argument we want, i.e. test -n should fail to parse */
|
||||
if (arg_idx >= end) {
|
||||
if (arg_idx >= end)
|
||||
{
|
||||
return error(L"Missing argument at index %u", arg_idx);
|
||||
}
|
||||
|
||||
|
@ -405,10 +463,13 @@ namespace test_expressions {
|
|||
}
|
||||
#endif
|
||||
|
||||
expression *test_parser::parse_binary_primary(unsigned int start, unsigned int end) {
|
||||
expression *test_parser::parse_binary_primary(unsigned int start, unsigned int end)
|
||||
{
|
||||
/* We need three arguments */
|
||||
for (unsigned int idx = start; idx < start + 3; idx++) {
|
||||
if (idx >= end) {
|
||||
for (unsigned int idx = start; idx < start + 3; idx++)
|
||||
{
|
||||
if (idx >= end)
|
||||
{
|
||||
return error(L"Missing argument at index %u", idx);
|
||||
}
|
||||
}
|
||||
|
@ -421,7 +482,8 @@ namespace test_expressions {
|
|||
return new binary_primary(info->tok, range_t(start, start + 3), arg(start), arg(start + 2));
|
||||
}
|
||||
|
||||
expression *test_parser::parse_parenthentical(unsigned int start, unsigned int end) {
|
||||
expression *test_parser::parse_parenthentical(unsigned int start, unsigned int end)
|
||||
{
|
||||
/* We need at least three arguments: open paren, argument, close paren */
|
||||
if (start + 3 >= end)
|
||||
return NULL;
|
||||
|
@ -440,11 +502,13 @@ namespace test_expressions {
|
|||
/* Parse a close paren */
|
||||
unsigned close_index = subexpr->range.end;
|
||||
assert(close_index <= end);
|
||||
if (close_index == end) {
|
||||
if (close_index == end)
|
||||
{
|
||||
return error(L"Missing close paren at index %u", close_index);
|
||||
}
|
||||
const token_info_t *close_paren = token_for_string(arg(close_index));
|
||||
if (close_paren->tok != test_paren_close) {
|
||||
if (close_paren->tok != test_paren_close)
|
||||
{
|
||||
return error(L"Expected close paren at index %u", close_index);
|
||||
}
|
||||
|
||||
|
@ -452,8 +516,10 @@ namespace test_expressions {
|
|||
return new parenthetical_expression(test_paren_open, range_t(start, close_index+1), subexpr);
|
||||
}
|
||||
|
||||
expression *test_parser::parse_primary(unsigned int start, unsigned int end) {
|
||||
if (start >= end) {
|
||||
expression *test_parser::parse_primary(unsigned int start, unsigned int end)
|
||||
{
|
||||
if (start >= end)
|
||||
{
|
||||
return error(L"Missing argument at index %u", start);
|
||||
}
|
||||
|
||||
|
@ -465,15 +531,18 @@ namespace test_expressions {
|
|||
return expr;
|
||||
}
|
||||
|
||||
expression *test_parser::parse_expression(unsigned int start, unsigned int end) {
|
||||
if (start >= end) {
|
||||
expression *test_parser::parse_expression(unsigned int start, unsigned int end)
|
||||
{
|
||||
if (start >= end)
|
||||
{
|
||||
return error(L"Missing argument at index %u", start);
|
||||
}
|
||||
|
||||
return parse_combining_expression(start, end);
|
||||
}
|
||||
|
||||
expression *test_parser::parse_args(const wcstring_list_t &args, wcstring &err) {
|
||||
expression *test_parser::parse_args(const wcstring_list_t &args, wcstring &err)
|
||||
{
|
||||
/* Empty list and one-arg list should be handled by caller */
|
||||
assert(args.size() > 1);
|
||||
|
||||
|
@ -482,7 +551,8 @@ namespace test_expressions {
|
|||
|
||||
/* Handle errors */
|
||||
bool errored = false;
|
||||
for (size_t i = 0; i < parser.errors.size(); i++) {
|
||||
for (size_t i = 0; i < parser.errors.size(); i++)
|
||||
{
|
||||
err.append(L"test: ");
|
||||
err.append(parser.errors.at(i));
|
||||
err.push_back(L'\n');
|
||||
|
@ -491,10 +561,12 @@ namespace test_expressions {
|
|||
break;
|
||||
}
|
||||
|
||||
if (! errored && result) {
|
||||
if (! errored && result)
|
||||
{
|
||||
/* It's also an error if there are any unused arguments. This is not detected by parse_expression() */
|
||||
assert(result->range.end <= args.size());
|
||||
if (result->range.end < args.size()) {
|
||||
if (result->range.end < args.size())
|
||||
{
|
||||
append_format(err, L"test: unexpected argument at index %lu: '%ls'\n", (unsigned long)result->range.end, args.at(result->range.end).c_str());
|
||||
delete result;
|
||||
result = NULL;
|
||||
|
@ -506,16 +578,20 @@ namespace test_expressions {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool unary_primary::evaluate(wcstring_list_t &errors) {
|
||||
bool unary_primary::evaluate(wcstring_list_t &errors)
|
||||
{
|
||||
return unary_primary_evaluate(token, arg, errors);
|
||||
}
|
||||
|
||||
bool binary_primary::evaluate(wcstring_list_t &errors) {
|
||||
bool binary_primary::evaluate(wcstring_list_t &errors)
|
||||
{
|
||||
return binary_primary_evaluate(token, arg_left, arg_right, errors);
|
||||
}
|
||||
|
||||
bool unary_operator::evaluate(wcstring_list_t &errors) {
|
||||
switch (token) {
|
||||
bool unary_operator::evaluate(wcstring_list_t &errors)
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case test_bang:
|
||||
assert(subject.get());
|
||||
return ! subject->evaluate(errors);
|
||||
|
@ -526,8 +602,10 @@ namespace test_expressions {
|
|||
}
|
||||
}
|
||||
|
||||
bool combining_expression::evaluate(wcstring_list_t &errors) {
|
||||
switch (token) {
|
||||
bool combining_expression::evaluate(wcstring_list_t &errors)
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case test_combine_and:
|
||||
case test_combine_or:
|
||||
{
|
||||
|
@ -541,20 +619,24 @@ namespace test_expressions {
|
|||
|
||||
size_t idx = 0, max = subjects.size();
|
||||
bool or_result = false;
|
||||
while (idx < max) {
|
||||
if (or_result) {
|
||||
while (idx < max)
|
||||
{
|
||||
if (or_result)
|
||||
{
|
||||
/* Short circuit */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Evaluate a stream of AND starting at given subject index. It may only have one element. */
|
||||
bool and_result = true;
|
||||
for (; idx < max; idx++) {
|
||||
for (; idx < max; idx++)
|
||||
{
|
||||
/* Evaluate it, short-circuiting */
|
||||
and_result = and_result && subjects.at(idx)->evaluate(errors);
|
||||
|
||||
/* If the combiner at this index (which corresponding to how we combine with the next subject) is not AND, then exit the loop */
|
||||
if (idx + 1 < max && combiners.at(idx) != test_combine_and) {
|
||||
if (idx + 1 < max && combiners.at(idx) != test_combine_and)
|
||||
{
|
||||
idx++;
|
||||
break;
|
||||
}
|
||||
|
@ -573,22 +655,26 @@ namespace test_expressions {
|
|||
}
|
||||
}
|
||||
|
||||
bool parenthetical_expression::evaluate(wcstring_list_t &errors) {
|
||||
bool parenthetical_expression::evaluate(wcstring_list_t &errors)
|
||||
{
|
||||
return contents->evaluate(errors);
|
||||
}
|
||||
|
||||
/* IEEE 1003.1 says nothing about what it means for two strings to be "algebraically equal". For example, should we interpret 0x10 as 0, 10, or 16? Here we use only base 10 and use wcstoll, which allows for leading + and -, and leading whitespace. This matches bash. */
|
||||
static bool parse_number(const wcstring &arg, long long *out) {
|
||||
static bool parse_number(const wcstring &arg, long long *out)
|
||||
{
|
||||
const wchar_t *str = arg.c_str();
|
||||
wchar_t *endptr = NULL;
|
||||
*out = wcstoll(str, &endptr, 10);
|
||||
return endptr && *endptr == L'\0';
|
||||
}
|
||||
|
||||
static bool binary_primary_evaluate(test_expressions::token_t token, const wcstring &left, const wcstring &right, wcstring_list_t &errors) {
|
||||
static bool binary_primary_evaluate(test_expressions::token_t token, const wcstring &left, const wcstring &right, wcstring_list_t &errors)
|
||||
{
|
||||
using namespace test_expressions;
|
||||
long long left_num, right_num;
|
||||
switch (token) {
|
||||
switch (token)
|
||||
{
|
||||
case test_string_equal:
|
||||
return left == right;
|
||||
|
||||
|
@ -620,11 +706,13 @@ namespace test_expressions {
|
|||
}
|
||||
|
||||
|
||||
static bool unary_primary_evaluate(test_expressions::token_t token, const wcstring &arg, wcstring_list_t &errors) {
|
||||
static bool unary_primary_evaluate(test_expressions::token_t token, const wcstring &arg, wcstring_list_t &errors)
|
||||
{
|
||||
using namespace test_expressions;
|
||||
struct stat buf;
|
||||
long long num;
|
||||
switch (token) {
|
||||
switch (token)
|
||||
{
|
||||
case test_filetype_b: // "-b", for block special files
|
||||
return !wstat(arg, &buf) && S_ISBLK(buf.st_mode);
|
||||
|
||||
|
@ -708,32 +796,43 @@ int builtin_test( parser_t &parser, wchar_t **argv )
|
|||
argc++;
|
||||
const wcstring_list_t args(argv + 1, argv + 1 + argc);
|
||||
|
||||
if (argc == 0) {
|
||||
if (argc == 0)
|
||||
{
|
||||
// Per 1003.1, exit false
|
||||
return BUILTIN_TEST_FAIL;
|
||||
} else if (argc == 1) {
|
||||
}
|
||||
else if (argc == 1)
|
||||
{
|
||||
// Per 1003.1, exit true if the arg is non-empty
|
||||
return args.at(0).empty() ? BUILTIN_TEST_FAIL : BUILTIN_TEST_SUCCESS;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try parsing. If expr is not nil, we are responsible for deleting it.
|
||||
wcstring err;
|
||||
expression *expr = test_parser::parse_args(args, err);
|
||||
if (! expr) {
|
||||
if (! expr)
|
||||
{
|
||||
#if 0
|
||||
printf("Oops! test was given args:\n");
|
||||
for (size_t i=0; i < argc; i++) {
|
||||
for (size_t i=0; i < argc; i++)
|
||||
{
|
||||
printf("\t%ls\n", args.at(i).c_str());
|
||||
}
|
||||
printf("and returned parse error: %ls\n", err.c_str());
|
||||
#endif
|
||||
builtin_show_error(err);
|
||||
return BUILTIN_TEST_FAIL;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
wcstring_list_t eval_errors;
|
||||
bool result = expr->evaluate(eval_errors);
|
||||
if (! eval_errors.empty()) {
|
||||
if (! eval_errors.empty())
|
||||
{
|
||||
printf("test returned eval errors:\n");
|
||||
for (size_t i=0; i < eval_errors.size(); i++) {
|
||||
for (size_t i=0; i < eval_errors.size(); i++)
|
||||
{
|
||||
printf("\t%ls\n", eval_errors.at(i).c_str());
|
||||
}
|
||||
}
|
||||
|
|
213
color.cpp
213
color.cpp
|
@ -4,40 +4,73 @@
|
|||
#include "color.h"
|
||||
#include "fallback.h"
|
||||
|
||||
bool rgb_color_t::try_parse_special(const wcstring &special) {
|
||||
bool rgb_color_t::try_parse_special(const wcstring &special)
|
||||
{
|
||||
bzero(&data, sizeof data);
|
||||
const wchar_t *name = special.c_str();
|
||||
if (! wcscasecmp(name, L"normal")) {
|
||||
if (! wcscasecmp(name, L"normal"))
|
||||
{
|
||||
this->type = type_normal;
|
||||
} else if (! wcscasecmp(name, L"reset")) {
|
||||
}
|
||||
else if (! wcscasecmp(name, L"reset"))
|
||||
{
|
||||
this->type = type_reset;
|
||||
} else if (! wcscasecmp(name, L"ignore")) {
|
||||
}
|
||||
else if (! wcscasecmp(name, L"ignore"))
|
||||
{
|
||||
this->type = type_ignore;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
this->type = type_none;
|
||||
}
|
||||
return this->type != type_none;
|
||||
}
|
||||
|
||||
static int parse_hex_digit(wchar_t x) {
|
||||
switch (x) {
|
||||
case L'0': return 0x0;
|
||||
case L'1': return 0x1;
|
||||
case L'2': return 0x2;
|
||||
case L'3': return 0x3;
|
||||
case L'4': return 0x4;
|
||||
case L'5': return 0x5;
|
||||
case L'6': return 0x6;
|
||||
case L'7': return 0x7;
|
||||
case L'8': return 0x8;
|
||||
case L'9': return 0x9;
|
||||
case L'a':case L'A': return 0xA;
|
||||
case L'b':case L'B': return 0xB;
|
||||
case L'c':case L'C': return 0xC;
|
||||
case L'd':case L'D': return 0xD;
|
||||
case L'e':case L'E': return 0xE;
|
||||
case L'f':case L'F': return 0xF;
|
||||
default: return -1;
|
||||
static int parse_hex_digit(wchar_t x)
|
||||
{
|
||||
switch (x)
|
||||
{
|
||||
case L'0':
|
||||
return 0x0;
|
||||
case L'1':
|
||||
return 0x1;
|
||||
case L'2':
|
||||
return 0x2;
|
||||
case L'3':
|
||||
return 0x3;
|
||||
case L'4':
|
||||
return 0x4;
|
||||
case L'5':
|
||||
return 0x5;
|
||||
case L'6':
|
||||
return 0x6;
|
||||
case L'7':
|
||||
return 0x7;
|
||||
case L'8':
|
||||
return 0x8;
|
||||
case L'9':
|
||||
return 0x9;
|
||||
case L'a':
|
||||
case L'A':
|
||||
return 0xA;
|
||||
case L'b':
|
||||
case L'B':
|
||||
return 0xB;
|
||||
case L'c':
|
||||
case L'C':
|
||||
return 0xC;
|
||||
case L'd':
|
||||
case L'D':
|
||||
return 0xD;
|
||||
case L'e':
|
||||
case L'E':
|
||||
return 0xE;
|
||||
case L'f':
|
||||
case L'F':
|
||||
return 0xF;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,15 +80,18 @@ static unsigned long squared_difference(long p1, long p2)
|
|||
return diff * diff;
|
||||
}
|
||||
|
||||
static unsigned char convert_color(const unsigned char rgb[3], const uint32_t *colors, size_t color_count) {
|
||||
static unsigned char convert_color(const unsigned char rgb[3], const uint32_t *colors, size_t color_count)
|
||||
{
|
||||
long r = rgb[0], g = rgb[1], b = rgb[2];
|
||||
unsigned long best_distance = (unsigned long)(-1);
|
||||
unsigned char best_index = (unsigned char)(-1);
|
||||
for (unsigned char idx = 0; idx < color_count; idx++) {
|
||||
for (unsigned char idx = 0; idx < color_count; idx++)
|
||||
{
|
||||
uint32_t color = colors[idx];
|
||||
long test_r = (color >> 16) & 0xFF, test_g = (color >> 8) & 0xFF, test_b = (color >> 0) & 0xFF;
|
||||
unsigned long distance = squared_difference(r, test_r) + squared_difference(g, test_g) + squared_difference(b, test_b);
|
||||
if (distance <= best_distance) {
|
||||
if (distance <= best_distance)
|
||||
{
|
||||
best_index = idx;
|
||||
best_distance = distance;
|
||||
}
|
||||
|
@ -64,7 +100,8 @@ static unsigned char convert_color(const unsigned char rgb[3], const uint32_t *c
|
|||
|
||||
}
|
||||
|
||||
bool rgb_color_t::try_parse_rgb(const wcstring &name) {
|
||||
bool rgb_color_t::try_parse_rgb(const wcstring &name)
|
||||
{
|
||||
bzero(&data, sizeof data);
|
||||
/* We support the following style of rgb formats (case insensitive):
|
||||
#FA3
|
||||
|
@ -81,17 +118,22 @@ bool rgb_color_t::try_parse_rgb(const wcstring &name) {
|
|||
|
||||
bool success = false;
|
||||
size_t i;
|
||||
if (len - digit_idx == 3) {
|
||||
if (len - digit_idx == 3)
|
||||
{
|
||||
// type FA3
|
||||
for (i=0; i < 3; i++) {
|
||||
for (i=0; i < 3; i++)
|
||||
{
|
||||
int val = parse_hex_digit(name.at(digit_idx++));
|
||||
if (val < 0) break;
|
||||
data.rgb[i] = val*16+val;
|
||||
}
|
||||
success = (i == 3);
|
||||
} else if (len - digit_idx == 6) {
|
||||
}
|
||||
else if (len - digit_idx == 6)
|
||||
{
|
||||
// type F3A035
|
||||
for (i=0; i < 3; i++) {
|
||||
for (i=0; i < 3; i++)
|
||||
{
|
||||
int hi = parse_hex_digit(name.at(digit_idx++));
|
||||
int lo = parse_hex_digit(name.at(digit_idx++));
|
||||
if (lo < 0 || hi < 0) break;
|
||||
|
@ -99,19 +141,22 @@ bool rgb_color_t::try_parse_rgb(const wcstring &name) {
|
|||
}
|
||||
success = (i == 3);
|
||||
}
|
||||
if (success) {
|
||||
if (success)
|
||||
{
|
||||
this->type = type_rgb;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
struct named_color_t {
|
||||
struct named_color_t
|
||||
{
|
||||
const wchar_t * name;
|
||||
unsigned char idx;
|
||||
unsigned char rgb[3];
|
||||
};
|
||||
|
||||
static const named_color_t named_colors[11] = {
|
||||
static const named_color_t named_colors[11] =
|
||||
{
|
||||
{L"black", 0, {0, 0, 0}},
|
||||
{L"red", 1, {0xFF, 0, 0}},
|
||||
{L"green", 2, {0, 0xFF, 0}},
|
||||
|
@ -125,11 +170,14 @@ static const named_color_t named_colors[11] = {
|
|||
{L"normal", 8, {0xFF, 0xFF, 0XFF}}
|
||||
};
|
||||
|
||||
bool rgb_color_t::try_parse_named(const wcstring &str) {
|
||||
bool rgb_color_t::try_parse_named(const wcstring &str)
|
||||
{
|
||||
bzero(&data, sizeof data);
|
||||
size_t max = sizeof named_colors / sizeof *named_colors;
|
||||
for (size_t idx=0; idx < max; idx++) {
|
||||
if (0 == wcscasecmp(str.c_str(), named_colors[idx].name)) {
|
||||
for (size_t idx=0; idx < max; idx++)
|
||||
{
|
||||
if (0 == wcscasecmp(str.c_str(), named_colors[idx].name))
|
||||
{
|
||||
data.name_idx = named_colors[idx].idx;
|
||||
this->type = type_named;
|
||||
return true;
|
||||
|
@ -138,29 +186,53 @@ bool rgb_color_t::try_parse_named(const wcstring &str) {
|
|||
return false;
|
||||
}
|
||||
|
||||
static const wchar_t *name_for_color_idx(unsigned char idx) {
|
||||
static const wchar_t *name_for_color_idx(unsigned char idx)
|
||||
{
|
||||
size_t max = sizeof named_colors / sizeof *named_colors;
|
||||
for (size_t i=0; i < max; i++) {
|
||||
if (named_colors[i].idx == idx) {
|
||||
for (size_t i=0; i < max; i++)
|
||||
{
|
||||
if (named_colors[i].idx == idx)
|
||||
{
|
||||
return named_colors[i].name;
|
||||
}
|
||||
}
|
||||
return L"unknown";
|
||||
}
|
||||
|
||||
rgb_color_t::rgb_color_t(unsigned char t, unsigned char i) : type(t), flags(), data() {
|
||||
rgb_color_t::rgb_color_t(unsigned char t, unsigned char i) : type(t), flags(), data()
|
||||
{
|
||||
data.name_idx = i;
|
||||
}
|
||||
|
||||
rgb_color_t rgb_color_t::normal() { return rgb_color_t(type_normal); }
|
||||
rgb_color_t rgb_color_t::reset() { return rgb_color_t(type_reset); }
|
||||
rgb_color_t rgb_color_t::ignore() { return rgb_color_t(type_ignore); }
|
||||
rgb_color_t rgb_color_t::none() { return rgb_color_t(type_none); }
|
||||
rgb_color_t rgb_color_t::white() { return rgb_color_t(type_named, 7); }
|
||||
rgb_color_t rgb_color_t::black() { return rgb_color_t(type_named, 0); }
|
||||
rgb_color_t rgb_color_t::normal()
|
||||
{
|
||||
return rgb_color_t(type_normal);
|
||||
}
|
||||
rgb_color_t rgb_color_t::reset()
|
||||
{
|
||||
return rgb_color_t(type_reset);
|
||||
}
|
||||
rgb_color_t rgb_color_t::ignore()
|
||||
{
|
||||
return rgb_color_t(type_ignore);
|
||||
}
|
||||
rgb_color_t rgb_color_t::none()
|
||||
{
|
||||
return rgb_color_t(type_none);
|
||||
}
|
||||
rgb_color_t rgb_color_t::white()
|
||||
{
|
||||
return rgb_color_t(type_named, 7);
|
||||
}
|
||||
rgb_color_t rgb_color_t::black()
|
||||
{
|
||||
return rgb_color_t(type_named, 0);
|
||||
}
|
||||
|
||||
static unsigned char term8_color_for_rgb(const unsigned char rgb[3]) {
|
||||
const uint32_t kColors[] = {
|
||||
static unsigned char term8_color_for_rgb(const unsigned char rgb[3])
|
||||
{
|
||||
const uint32_t kColors[] =
|
||||
{
|
||||
0x000000, //Black
|
||||
0xFF0000, //Red
|
||||
0x00FF00, //Green
|
||||
|
@ -173,8 +245,10 @@ static unsigned char term8_color_for_rgb(const unsigned char rgb[3]) {
|
|||
return convert_color(rgb, kColors, sizeof kColors / sizeof *kColors);
|
||||
}
|
||||
|
||||
static unsigned char term256_color_for_rgb(const unsigned char rgb[3]) {
|
||||
const uint32_t kColors[240] = {
|
||||
static unsigned char term256_color_for_rgb(const unsigned char rgb[3])
|
||||
{
|
||||
const uint32_t kColors[240] =
|
||||
{
|
||||
0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
|
||||
0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
|
||||
0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
|
||||
|
@ -209,44 +283,57 @@ static unsigned char term256_color_for_rgb(const unsigned char rgb[3]) {
|
|||
return 16 + convert_color(rgb, kColors, sizeof kColors / sizeof *kColors);
|
||||
}
|
||||
|
||||
unsigned char rgb_color_t::to_term256_index() const {
|
||||
unsigned char rgb_color_t::to_term256_index() const
|
||||
{
|
||||
assert(type == type_rgb);
|
||||
return term256_color_for_rgb(data.rgb);
|
||||
}
|
||||
|
||||
unsigned char rgb_color_t::to_name_index() const {
|
||||
unsigned char rgb_color_t::to_name_index() const
|
||||
{
|
||||
assert(type == type_named || type == type_rgb);
|
||||
if (type == type_named) {
|
||||
if (type == type_named)
|
||||
{
|
||||
return data.name_idx;
|
||||
} else if (type == type_rgb) {
|
||||
}
|
||||
else if (type == type_rgb)
|
||||
{
|
||||
return term8_color_for_rgb(data.rgb);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* This is an error */
|
||||
return (unsigned char)(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void rgb_color_t::parse(const wcstring &str) {
|
||||
void rgb_color_t::parse(const wcstring &str)
|
||||
{
|
||||
bool success = false;
|
||||
if (! success) success = try_parse_special(str);
|
||||
if (! success) success = try_parse_named(str);
|
||||
if (! success) success = try_parse_rgb(str);
|
||||
if (! success) {
|
||||
if (! success)
|
||||
{
|
||||
bzero(this->data.rgb, sizeof this->data.rgb);
|
||||
this->type = type_none;
|
||||
}
|
||||
}
|
||||
|
||||
rgb_color_t::rgb_color_t(const wcstring &str) {
|
||||
rgb_color_t::rgb_color_t(const wcstring &str)
|
||||
{
|
||||
this->parse(str);
|
||||
}
|
||||
|
||||
rgb_color_t::rgb_color_t(const std::string &str) {
|
||||
rgb_color_t::rgb_color_t(const std::string &str)
|
||||
{
|
||||
this->parse(str2wcstring(str));
|
||||
}
|
||||
|
||||
wcstring rgb_color_t::description() const {
|
||||
switch (type) {
|
||||
wcstring rgb_color_t::description() const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case type_none:
|
||||
return L"none";
|
||||
case type_named:
|
||||
|
|
75
color.h
75
color.h
|
@ -10,10 +10,12 @@
|
|||
|
||||
|
||||
/* A type that represents a color. We work hard to keep it at a size of 4 bytes. */
|
||||
class rgb_color_t {
|
||||
class rgb_color_t
|
||||
{
|
||||
|
||||
/* Types */
|
||||
enum {
|
||||
enum
|
||||
{
|
||||
type_none,
|
||||
type_named,
|
||||
type_rgb,
|
||||
|
@ -24,13 +26,15 @@ class rgb_color_t {
|
|||
unsigned char type:4;
|
||||
|
||||
/* Flags */
|
||||
enum {
|
||||
enum
|
||||
{
|
||||
flag_bold = 1 << 0,
|
||||
flag_underline = 1 << 1
|
||||
};
|
||||
unsigned char flags:4;
|
||||
|
||||
union {
|
||||
union
|
||||
{
|
||||
unsigned char name_idx; //0-10
|
||||
unsigned char rgb[3];
|
||||
} data;
|
||||
|
@ -78,25 +82,46 @@ class rgb_color_t {
|
|||
static rgb_color_t none();
|
||||
|
||||
/** Returns whether the color is the ignore special color */
|
||||
bool is_ignore(void) const { return type == type_ignore; }
|
||||
bool is_ignore(void) const
|
||||
{
|
||||
return type == type_ignore;
|
||||
}
|
||||
|
||||
/** Returns whether the color is the normal special color */
|
||||
bool is_normal(void) const { return type == type_normal; }
|
||||
bool is_normal(void) const
|
||||
{
|
||||
return type == type_normal;
|
||||
}
|
||||
|
||||
/** Returns whether the color is the reset special color */
|
||||
bool is_reset(void) const { return type == type_reset; }
|
||||
bool is_reset(void) const
|
||||
{
|
||||
return type == type_reset;
|
||||
}
|
||||
|
||||
/** Returns whether the color is the none special color */
|
||||
bool is_none(void) const { return type == type_none; }
|
||||
bool is_none(void) const
|
||||
{
|
||||
return type == type_none;
|
||||
}
|
||||
|
||||
/** Returns whether the color is a named color (like "magenta") */
|
||||
bool is_named(void) const { return type == type_named; }
|
||||
bool is_named(void) const
|
||||
{
|
||||
return type == type_named;
|
||||
}
|
||||
|
||||
/** Returns whether the color is specified via RGB components */
|
||||
bool is_rgb(void) const { return type == type_rgb; }
|
||||
bool is_rgb(void) const
|
||||
{
|
||||
return type == type_rgb;
|
||||
}
|
||||
|
||||
/** Returns whether the color is special, that is, not rgb or named */
|
||||
bool is_special(void) const { return type != type_named && type != type_rgb; }
|
||||
bool is_special(void) const
|
||||
{
|
||||
return type != type_named && type != type_rgb;
|
||||
}
|
||||
|
||||
/** Returns a description of the color */
|
||||
wcstring description() const;
|
||||
|
@ -108,24 +133,40 @@ class rgb_color_t {
|
|||
unsigned char to_term256_index() const;
|
||||
|
||||
/** Returns whether the color is bold */
|
||||
bool is_bold() const { return flags & flag_bold; }
|
||||
bool is_bold() const
|
||||
{
|
||||
return flags & flag_bold;
|
||||
}
|
||||
|
||||
/** Set whether the color is bold */
|
||||
void set_bold(bool x) { if (x) flags |= flag_bold; else flags &= ~flag_bold; }
|
||||
void set_bold(bool x)
|
||||
{
|
||||
if (x) flags |= flag_bold;
|
||||
else flags &= ~flag_bold;
|
||||
}
|
||||
|
||||
/** Returns whether the color is underlined */
|
||||
bool is_underline() const { return !! (flags & flag_underline); }
|
||||
bool is_underline() const
|
||||
{
|
||||
return !!(flags & flag_underline);
|
||||
}
|
||||
|
||||
/** Set whether the color is underlined */
|
||||
void set_underline(bool x) { if (x) flags |= flag_underline; else flags &= ~flag_underline; }
|
||||
void set_underline(bool x)
|
||||
{
|
||||
if (x) flags |= flag_underline;
|
||||
else flags &= ~flag_underline;
|
||||
}
|
||||
|
||||
/** Compare two colors for equality */
|
||||
bool operator==(const rgb_color_t &other) const {
|
||||
bool operator==(const rgb_color_t &other) const
|
||||
{
|
||||
return type == other.type && ! memcmp(&data, &other.data, sizeof data);
|
||||
}
|
||||
|
||||
/** Compare two colors for inequality */
|
||||
bool operator!=(const rgb_color_t &other) const {
|
||||
bool operator!=(const rgb_color_t &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
|
199
common.cpp
199
common.cpp
|
@ -261,22 +261,28 @@ char *wcs2str( const wchar_t *in )
|
|||
char *out;
|
||||
size_t desired_size = MAX_UTF8_BYTES*wcslen(in)+1;
|
||||
char local_buff[512];
|
||||
if (desired_size <= sizeof local_buff / sizeof *local_buff) {
|
||||
if (desired_size <= sizeof local_buff / sizeof *local_buff)
|
||||
{
|
||||
// convert into local buff, then use strdup() so we don't waste malloc'd space
|
||||
char *result = wcs2str_internal(in, local_buff);
|
||||
if (result) {
|
||||
if (result)
|
||||
{
|
||||
// It converted into the local buffer, so copy it
|
||||
result = strdup(result);
|
||||
if (! result) {
|
||||
if (! result)
|
||||
{
|
||||
DIE_MEM();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// here we fall into the bad case of allocating a buffer probably much larger than necessary
|
||||
out = (char *)malloc(MAX_UTF8_BYTES*wcslen(in)+1);
|
||||
if (!out) {
|
||||
if (!out)
|
||||
{
|
||||
DIE_MEM();
|
||||
}
|
||||
return wcs2str_internal(in, out);
|
||||
|
@ -387,19 +393,25 @@ wcstring vformat_string(const wchar_t *format, va_list va_orig)
|
|||
size_t size = 0;
|
||||
wchar_t *buff = NULL;
|
||||
int status = -1;
|
||||
while (status < 0) {
|
||||
while (status < 0)
|
||||
{
|
||||
/* Reallocate if necessary */
|
||||
if (size == 0) {
|
||||
if (size == 0)
|
||||
{
|
||||
buff = static_buff;
|
||||
size = sizeof static_buff;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
size *= 2;
|
||||
if (size >= max_size) {
|
||||
if (size >= max_size)
|
||||
{
|
||||
buff[0] = '\0';
|
||||
break;
|
||||
}
|
||||
buff = (wchar_t *)realloc((buff == static_buff ? NULL : buff), size);
|
||||
if (buff == NULL) {
|
||||
if (buff == NULL)
|
||||
{
|
||||
DIE_MEM();
|
||||
}
|
||||
}
|
||||
|
@ -498,7 +510,8 @@ wcstring wsetlocale(int category, const wchar_t *locale)
|
|||
{
|
||||
|
||||
char *lang = NULL;
|
||||
if (locale){
|
||||
if (locale)
|
||||
{
|
||||
lang = wcs2str(locale);
|
||||
}
|
||||
char * res = setlocale(category,lang);
|
||||
|
@ -603,9 +616,11 @@ ssize_t write_loop(int fd, const char *buff, size_t count)
|
|||
ssize_t read_loop(int fd, void *buff, size_t count)
|
||||
{
|
||||
ssize_t result;
|
||||
do {
|
||||
do
|
||||
{
|
||||
result = read(fd, buff, count);
|
||||
} while (result < 0 && (errno == EAGAIN || errno == EINTR));
|
||||
}
|
||||
while (result < 0 && (errno == EAGAIN || errno == EINTR));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -670,14 +685,16 @@ void debug_safe(int level, const char *msg, const char *param1, const char *para
|
|||
|
||||
size_t param_idx = 0;
|
||||
const char *cursor = msg;
|
||||
while (*cursor != '\0') {
|
||||
while (*cursor != '\0')
|
||||
{
|
||||
const char *end = strchr(cursor, '%');
|
||||
if (end == NULL)
|
||||
end = cursor + strlen(cursor);
|
||||
|
||||
write(STDERR_FILENO, cursor, end - cursor);
|
||||
|
||||
if (end[0] == '%' && end[1] == 's') {
|
||||
if (end[0] == '%' && end[1] == 's')
|
||||
{
|
||||
/* Handle a format string */
|
||||
assert(param_idx < sizeof params / sizeof *params);
|
||||
const char *format = params[param_idx++];
|
||||
|
@ -685,10 +702,14 @@ void debug_safe(int level, const char *msg, const char *param1, const char *para
|
|||
format = "(null)";
|
||||
write(STDERR_FILENO, format, strlen(format));
|
||||
cursor = end + 2;
|
||||
} else if (end[0] == '\0') {
|
||||
}
|
||||
else if (end[0] == '\0')
|
||||
{
|
||||
/* Must be at the end of the string */
|
||||
cursor = end;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Some other format specifier, just skip it */
|
||||
cursor = end + 1;
|
||||
}
|
||||
|
@ -700,17 +721,22 @@ void debug_safe(int level, const char *msg, const char *param1, const char *para
|
|||
errno = errno_old;
|
||||
}
|
||||
|
||||
void format_long_safe(char buff[128], long val) {
|
||||
if (val == 0) {
|
||||
void format_long_safe(char buff[128], long val)
|
||||
{
|
||||
if (val == 0)
|
||||
{
|
||||
strcpy(buff, "0");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Generate the string in reverse */
|
||||
size_t idx = 0;
|
||||
bool negative = (val < 0);
|
||||
|
||||
/* Note that we can't just negate val if it's negative, because it may be the most negative value. We do rely on round-towards-zero division though. */
|
||||
|
||||
while (val != 0) {
|
||||
while (val != 0)
|
||||
{
|
||||
long rem = val % 10;
|
||||
buff[idx++] = '0' + (rem < 0 ? -rem : rem);
|
||||
val /= 10;
|
||||
|
@ -720,7 +746,8 @@ void format_long_safe(char buff[128], long val) {
|
|||
buff[idx] = 0;
|
||||
|
||||
size_t left = 0, right = idx - 1;
|
||||
while (left < right) {
|
||||
while (left < right)
|
||||
{
|
||||
char tmp = buff[left];
|
||||
buff[left++] = buff[right];
|
||||
buff[right--] = tmp;
|
||||
|
@ -728,15 +755,20 @@ void format_long_safe(char buff[128], long val) {
|
|||
}
|
||||
}
|
||||
|
||||
void format_long_safe(wchar_t buff[128], long val) {
|
||||
if (val == 0) {
|
||||
void format_long_safe(wchar_t buff[128], long val)
|
||||
{
|
||||
if (val == 0)
|
||||
{
|
||||
wcscpy(buff, L"0");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Generate the string in reverse */
|
||||
size_t idx = 0;
|
||||
bool negative = (val < 0);
|
||||
|
||||
while (val > 0) {
|
||||
while (val > 0)
|
||||
{
|
||||
long rem = val % 10;
|
||||
/* Here we're assuming that wide character digits are contiguous - is that a correct assumption? */
|
||||
buff[idx++] = L'0' + (wchar_t)(rem < 0 ? -rem : rem);
|
||||
|
@ -747,7 +779,8 @@ void format_long_safe(wchar_t buff[128], long val) {
|
|||
buff[idx] = 0;
|
||||
|
||||
size_t left = 0, right = idx - 1;
|
||||
while (left < right) {
|
||||
while (left < right)
|
||||
{
|
||||
wchar_t tmp = buff[left];
|
||||
buff[left++] = buff[right];
|
||||
buff[right--] = tmp;
|
||||
|
@ -1045,7 +1078,8 @@ wchar_t *escape( const wchar_t *in_orig, escape_flags_t flags )
|
|||
return out;
|
||||
}
|
||||
|
||||
wcstring escape_string( const wcstring &in, escape_flags_t flags ) {
|
||||
wcstring escape_string(const wcstring &in, escape_flags_t flags)
|
||||
{
|
||||
wchar_t *tmp = escape(in.c_str(), flags);
|
||||
wcstring result(tmp);
|
||||
free(tmp);
|
||||
|
@ -1596,7 +1630,8 @@ bool unescape_string(wcstring &str, int escape_special)
|
|||
{
|
||||
bool success = false;
|
||||
wchar_t *result = unescape(str.c_str(), escape_special);
|
||||
if (result) {
|
||||
if (result)
|
||||
{
|
||||
str.replace(str.begin(), str.end(), result);
|
||||
free(result);
|
||||
success = true;
|
||||
|
@ -1633,7 +1668,8 @@ int common_get_height()
|
|||
void tokenize_variable_array(const wcstring &val, std::vector<wcstring> &out)
|
||||
{
|
||||
size_t pos = 0, end = val.size();
|
||||
while (pos < end) {
|
||||
while (pos < end)
|
||||
{
|
||||
size_t next_pos = val.find(ARRAY_SEP, pos);
|
||||
if (next_pos == wcstring::npos) break;
|
||||
out.push_back(val.substr(pos, next_pos - pos));
|
||||
|
@ -1655,17 +1691,20 @@ bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &val
|
|||
return prefix_size <= value.size() && value.compare(0, prefix_size, proposed_prefix) == 0;
|
||||
}
|
||||
|
||||
bool string_prefixes_string_case_insensitive(const wcstring &proposed_prefix, const wcstring &value) {
|
||||
bool string_prefixes_string_case_insensitive(const wcstring &proposed_prefix, const wcstring &value)
|
||||
{
|
||||
size_t prefix_size = proposed_prefix.size();
|
||||
return prefix_size <= value.size() && wcsncasecmp(proposed_prefix.c_str(), value.c_str(), prefix_size) == 0;
|
||||
}
|
||||
|
||||
bool string_suffixes_string(const wcstring &proposed_suffix, const wcstring &value) {
|
||||
bool string_suffixes_string(const wcstring &proposed_suffix, const wcstring &value)
|
||||
{
|
||||
size_t suffix_size = proposed_suffix.size();
|
||||
return suffix_size <= value.size() && value.compare(value.size() - suffix_size, suffix_size, proposed_suffix) == 0;
|
||||
}
|
||||
|
||||
bool string_suffixes_string(const wchar_t *proposed_suffix, const wcstring &value) {
|
||||
bool string_suffixes_string(const wchar_t *proposed_suffix, const wcstring &value)
|
||||
{
|
||||
size_t suffix_size = wcslen(proposed_suffix);
|
||||
return suffix_size <= value.size() && value.compare(value.size() - suffix_size, suffix_size, proposed_suffix) == 0;
|
||||
}
|
||||
|
@ -1724,7 +1763,8 @@ void bugreport()
|
|||
wcstring format_size(long long sz)
|
||||
{
|
||||
wcstring result;
|
||||
const wchar_t *sz_name[]= {
|
||||
const wchar_t *sz_name[]=
|
||||
{
|
||||
L"kB", L"MB", L"GB", L"TB", L"PB", L"EB", L"ZB", L"YB", 0
|
||||
};
|
||||
|
||||
|
@ -1763,10 +1803,12 @@ wcstring format_size(long long sz)
|
|||
}
|
||||
|
||||
/* Crappy function to extract the most significant digit of an unsigned long long value */
|
||||
static char extract_most_significant_digit(unsigned long long *xp) {
|
||||
static char extract_most_significant_digit(unsigned long long *xp)
|
||||
{
|
||||
unsigned long long place_value = 1;
|
||||
unsigned long long x = *xp;
|
||||
while (x >= 10) {
|
||||
while (x >= 10)
|
||||
{
|
||||
x /= 10;
|
||||
place_value *= 10;
|
||||
}
|
||||
|
@ -1774,26 +1816,30 @@ static char extract_most_significant_digit(unsigned long long *xp) {
|
|||
return x + '0';
|
||||
}
|
||||
|
||||
void append_ull(char *buff, unsigned long long val, size_t *inout_idx, size_t max_len) {
|
||||
void append_ull(char *buff, unsigned long long val, size_t *inout_idx, size_t max_len)
|
||||
{
|
||||
size_t idx = *inout_idx;
|
||||
while (val > 0 && idx < max_len)
|
||||
buff[idx++] = extract_most_significant_digit(&val);
|
||||
*inout_idx = idx;
|
||||
}
|
||||
|
||||
void append_str(char *buff, const char *str, size_t *inout_idx, size_t max_len) {
|
||||
void append_str(char *buff, const char *str, size_t *inout_idx, size_t max_len)
|
||||
{
|
||||
size_t idx = *inout_idx;
|
||||
while (*str && idx < max_len)
|
||||
buff[idx++] = *str++;
|
||||
*inout_idx = idx;
|
||||
}
|
||||
|
||||
void format_size_safe(char buff[128], unsigned long long sz) {
|
||||
void format_size_safe(char buff[128], unsigned long long sz)
|
||||
{
|
||||
const size_t buff_size = 128;
|
||||
const size_t max_len = buff_size - 1; //need to leave room for a null terminator
|
||||
bzero(buff, buff_size);
|
||||
size_t idx = 0;
|
||||
const char * const sz_name[]= {
|
||||
const char * const sz_name[]=
|
||||
{
|
||||
"kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", NULL
|
||||
};
|
||||
if (sz < 1)
|
||||
|
@ -1865,18 +1911,21 @@ double timef()
|
|||
return (double)tv.tv_sec + 0.000001*tv.tv_usec;
|
||||
}
|
||||
|
||||
void exit_without_destructors(int code) {
|
||||
void exit_without_destructors(int code)
|
||||
{
|
||||
_exit(code);
|
||||
}
|
||||
|
||||
/* Helper function to convert from a null_terminated_array_t<wchar_t> to a null_terminated_array_t<char_t> */
|
||||
null_terminated_array_t<char> convert_wide_array_to_narrow(const null_terminated_array_t<wchar_t> &wide_arr) {
|
||||
null_terminated_array_t<char> convert_wide_array_to_narrow(const null_terminated_array_t<wchar_t> &wide_arr)
|
||||
{
|
||||
const wchar_t *const *arr = wide_arr.get();
|
||||
if (! arr)
|
||||
return null_terminated_array_t<char>();
|
||||
|
||||
std::vector<std::string> list;
|
||||
for (size_t i=0; arr[i]; i++) {
|
||||
for (size_t i=0; arr[i]; i++)
|
||||
{
|
||||
list.push_back(wcs2string(arr[i]));
|
||||
}
|
||||
return null_terminated_array_t<char>(list);
|
||||
|
@ -1884,16 +1933,22 @@ null_terminated_array_t<char> convert_wide_array_to_narrow(const null_terminated
|
|||
|
||||
void append_path_component(wcstring &path, const wcstring &component)
|
||||
{
|
||||
if (path.empty() || component.empty()) {
|
||||
if (path.empty() || component.empty())
|
||||
{
|
||||
path.append(component);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t path_len = path.size();
|
||||
bool path_slash = path.at(path_len-1) == L'/';
|
||||
bool comp_slash = component.at(0) == L'/';
|
||||
if (! path_slash && ! comp_slash) {
|
||||
if (! path_slash && ! comp_slash)
|
||||
{
|
||||
// Need a slash
|
||||
path.push_back(L'/');
|
||||
} else if (path_slash && comp_slash) {
|
||||
}
|
||||
else if (path_slash && comp_slash)
|
||||
{
|
||||
// Too many slashes
|
||||
path.erase(path_len - 1, 1);
|
||||
}
|
||||
|
@ -1902,15 +1957,20 @@ void append_path_component(wcstring &path, const wcstring &component)
|
|||
}
|
||||
|
||||
extern "C" {
|
||||
__attribute__((noinline)) void debug_thread_error(void) { while (1) sleep(9999999); }
|
||||
__attribute__((noinline)) void debug_thread_error(void)
|
||||
{
|
||||
while (1) sleep(9999999);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void set_main_thread() {
|
||||
void set_main_thread()
|
||||
{
|
||||
main_thread_id = pthread_self();
|
||||
}
|
||||
|
||||
void configure_thread_assertions_for_testing(void) {
|
||||
void configure_thread_assertions_for_testing(void)
|
||||
{
|
||||
thread_assertions_configured_for_testing = true;
|
||||
}
|
||||
|
||||
|
@ -1920,12 +1980,14 @@ static pid_t initial_pid = 0;
|
|||
/* Be able to restore the term's foreground process group */
|
||||
static pid_t initial_foreground_process_group = -1;
|
||||
|
||||
bool is_forked_child(void) {
|
||||
bool is_forked_child(void)
|
||||
{
|
||||
/* Just bail if nobody's called setup_fork_guards - e.g. fishd */
|
||||
if (! initial_pid) return false;
|
||||
|
||||
bool is_child_of_fork = (getpid() != initial_pid);
|
||||
if (is_child_of_fork) {
|
||||
if (is_child_of_fork)
|
||||
{
|
||||
printf("Uh-oh: %d\n", getpid());
|
||||
while (1) sleep(10000);
|
||||
}
|
||||
|
@ -1951,14 +2013,16 @@ void restore_term_foreground_process_group(void)
|
|||
}
|
||||
}
|
||||
|
||||
bool is_main_thread() {
|
||||
bool is_main_thread()
|
||||
{
|
||||
assert(main_thread_id != 0);
|
||||
return main_thread_id == pthread_self();
|
||||
}
|
||||
|
||||
void assert_is_main_thread(const char *who)
|
||||
{
|
||||
if (! is_main_thread() && ! thread_assertions_configured_for_testing) {
|
||||
if (! is_main_thread() && ! thread_assertions_configured_for_testing)
|
||||
{
|
||||
fprintf(stderr, "Warning: %s called off of main thread. Break on debug_thread_error to debug.\n", who);
|
||||
debug_thread_error();
|
||||
}
|
||||
|
@ -1966,7 +2030,8 @@ void assert_is_main_thread(const char *who)
|
|||
|
||||
void assert_is_not_forked_child(const char *who)
|
||||
{
|
||||
if (is_forked_child()) {
|
||||
if (is_forked_child())
|
||||
{
|
||||
fprintf(stderr, "Warning: %s called in a forked child. Break on debug_thread_error to debug.\n", who);
|
||||
debug_thread_error();
|
||||
}
|
||||
|
@ -1974,7 +2039,8 @@ void assert_is_not_forked_child(const char *who)
|
|||
|
||||
void assert_is_background_thread(const char *who)
|
||||
{
|
||||
if (is_main_thread() && ! thread_assertions_configured_for_testing) {
|
||||
if (is_main_thread() && ! thread_assertions_configured_for_testing)
|
||||
{
|
||||
fprintf(stderr, "Warning: %s called on the main thread (may block!). Break on debug_thread_error to debug.\n", who);
|
||||
debug_thread_error();
|
||||
}
|
||||
|
@ -1983,32 +2049,37 @@ void assert_is_background_thread(const char *who)
|
|||
void assert_is_locked(void *vmutex, const char *who, const char *caller)
|
||||
{
|
||||
pthread_mutex_t *mutex = static_cast<pthread_mutex_t*>(vmutex);
|
||||
if (0 == pthread_mutex_trylock(mutex)) {
|
||||
if (0 == pthread_mutex_trylock(mutex))
|
||||
{
|
||||
fprintf(stderr, "Warning: %s is not locked when it should be in '%s'. Break on debug_thread_error to debug.\n", who, caller);
|
||||
debug_thread_error();
|
||||
pthread_mutex_unlock(mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void scoped_lock::lock(void) {
|
||||
void scoped_lock::lock(void)
|
||||
{
|
||||
assert(! locked);
|
||||
assert(! is_forked_child());
|
||||
VOMIT_ON_FAILURE(pthread_mutex_lock(lock_obj));
|
||||
locked = true;
|
||||
}
|
||||
|
||||
void scoped_lock::unlock(void) {
|
||||
void scoped_lock::unlock(void)
|
||||
{
|
||||
assert(locked);
|
||||
assert(! is_forked_child());
|
||||
VOMIT_ON_FAILURE(pthread_mutex_unlock(lock_obj));
|
||||
locked = false;
|
||||
}
|
||||
|
||||
scoped_lock::scoped_lock(pthread_mutex_t &mutex) : lock_obj(&mutex), locked(false) {
|
||||
scoped_lock::scoped_lock(pthread_mutex_t &mutex) : lock_obj(&mutex), locked(false)
|
||||
{
|
||||
this->lock();
|
||||
}
|
||||
|
||||
scoped_lock::~scoped_lock() {
|
||||
scoped_lock::~scoped_lock()
|
||||
{
|
||||
if (locked) this->unlock();
|
||||
}
|
||||
|
||||
|
@ -2023,13 +2094,15 @@ wcstokenizer::wcstokenizer(const wcstring &s, const wcstring &separator) :
|
|||
state = NULL;
|
||||
}
|
||||
|
||||
bool wcstokenizer::next(wcstring &result) {
|
||||
bool wcstokenizer::next(wcstring &result)
|
||||
{
|
||||
wchar_t *tmp = wcstok(str, sep.c_str(), &state);
|
||||
str = NULL;
|
||||
if (tmp) result = tmp;
|
||||
return tmp != NULL;
|
||||
}
|
||||
|
||||
wcstokenizer::~wcstokenizer() {
|
||||
wcstokenizer::~wcstokenizer()
|
||||
{
|
||||
free(buffer);
|
||||
}
|
||||
|
|
112
common.h
112
common.h
|
@ -64,7 +64,8 @@ typedef std::vector<wcstring> wcstring_list_t;
|
|||
#define UNESCAPE_INCOMPLETE 2
|
||||
|
||||
/* Flags for the escape() and escape_string() functions */
|
||||
enum {
|
||||
enum
|
||||
{
|
||||
/** Escape all characters, including magic characters like the semicolon */
|
||||
ESCAPE_ALL = 1 << 0,
|
||||
|
||||
|
@ -294,7 +295,8 @@ void format_long_safe(wchar_t buff[128], long val);
|
|||
|
||||
|
||||
template<typename T>
|
||||
T from_string(const wcstring &x) {
|
||||
T from_string(const wcstring &x)
|
||||
{
|
||||
T result;
|
||||
std::wstringstream stream(x);
|
||||
stream >> result;
|
||||
|
@ -302,7 +304,8 @@ T from_string(const wcstring &x) {
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
T from_string(const std::string &x) {
|
||||
T from_string(const std::string &x)
|
||||
{
|
||||
T result = T();
|
||||
std::stringstream stream(x);
|
||||
stream >> result;
|
||||
|
@ -310,7 +313,8 @@ T from_string(const std::string &x) {
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
wcstring to_string(const T &x) {
|
||||
wcstring to_string(const T &x)
|
||||
{
|
||||
std::wstringstream stream;
|
||||
stream << x;
|
||||
return stream.str();
|
||||
|
@ -318,54 +322,67 @@ wcstring to_string(const T &x) {
|
|||
|
||||
/* wstringstream is a huge memory pig. Let's provide some specializations where we can. */
|
||||
template<>
|
||||
inline wcstring to_string(const long &x) {
|
||||
inline wcstring to_string(const long &x)
|
||||
{
|
||||
wchar_t buff[128];
|
||||
format_long_safe(buff, x);
|
||||
return wcstring(buff);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool from_string(const std::string &x) {
|
||||
inline bool from_string(const std::string &x)
|
||||
{
|
||||
return ! x.empty() && strchr("YTyt1", x.at(0));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool from_string(const wcstring &x) {
|
||||
inline bool from_string(const wcstring &x)
|
||||
{
|
||||
return ! x.empty() && wcschr(L"YTyt1", x.at(0));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline wcstring to_string(const int &x) {
|
||||
inline wcstring to_string(const int &x)
|
||||
{
|
||||
return to_string(static_cast<long>(x));
|
||||
}
|
||||
|
||||
|
||||
/* Helper class for managing a null-terminated array of null-terminated strings (of some char type) */
|
||||
template <typename CharType_t>
|
||||
class null_terminated_array_t {
|
||||
class null_terminated_array_t
|
||||
{
|
||||
CharType_t **array;
|
||||
|
||||
typedef std::basic_string<CharType_t> string_t;
|
||||
typedef std::vector<string_t> string_list_t;
|
||||
|
||||
void swap(null_terminated_array_t<CharType_t> &him) { std::swap(array, him.array); }
|
||||
void swap(null_terminated_array_t<CharType_t> &him)
|
||||
{
|
||||
std::swap(array, him.array);
|
||||
}
|
||||
|
||||
/* Silly function to get the length of a null terminated array of...something */
|
||||
template <typename T>
|
||||
static size_t count_not_null(const T *arr) {
|
||||
static size_t count_not_null(const T *arr)
|
||||
{
|
||||
size_t len;
|
||||
for (len=0; arr[len] != T(0); len++)
|
||||
;
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
size_t size() const
|
||||
{
|
||||
return count_not_null(array);
|
||||
}
|
||||
|
||||
void free(void) {
|
||||
if (array != NULL) {
|
||||
for (size_t i = 0; array[i] != NULL; i++) {
|
||||
void free(void)
|
||||
{
|
||||
if (array != NULL)
|
||||
{
|
||||
for (size_t i = 0; array[i] != NULL; i++)
|
||||
{
|
||||
delete [] array[i];
|
||||
}
|
||||
delete [] array;
|
||||
|
@ -375,29 +392,39 @@ class null_terminated_array_t {
|
|||
|
||||
public:
|
||||
null_terminated_array_t() : array(NULL) { }
|
||||
null_terminated_array_t(const string_list_t &argv) : array(NULL) { this->set(argv); }
|
||||
~null_terminated_array_t() { this->free(); }
|
||||
null_terminated_array_t(const string_list_t &argv) : array(NULL)
|
||||
{
|
||||
this->set(argv);
|
||||
}
|
||||
~null_terminated_array_t()
|
||||
{
|
||||
this->free();
|
||||
}
|
||||
|
||||
/** operator=. Notice the pass-by-value parameter. */
|
||||
null_terminated_array_t& operator=(null_terminated_array_t rhs) {
|
||||
null_terminated_array_t& operator=(null_terminated_array_t rhs)
|
||||
{
|
||||
if (this != &rhs)
|
||||
this->swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Copy constructor. */
|
||||
null_terminated_array_t(const null_terminated_array_t &him) : array(NULL) {
|
||||
null_terminated_array_t(const null_terminated_array_t &him) : array(NULL)
|
||||
{
|
||||
this->set(him.array);
|
||||
}
|
||||
|
||||
void set(const string_list_t &argv) {
|
||||
void set(const string_list_t &argv)
|
||||
{
|
||||
/* Get rid of the old argv */
|
||||
this->free();
|
||||
|
||||
/* Allocate our null-terminated array of null-terminated strings */
|
||||
size_t i, count = argv.size();
|
||||
this->array = new CharType_t * [count + 1];
|
||||
for (i=0; i < count; i++) {
|
||||
for (i=0; i < count; i++)
|
||||
{
|
||||
const string_t &str = argv.at(i);
|
||||
this->array[i] = new CharType_t [1 + str.size()];
|
||||
std::copy(str.begin(), str.end(), this->array[i]);
|
||||
|
@ -406,7 +433,8 @@ class null_terminated_array_t {
|
|||
this->array[count] = NULL;
|
||||
}
|
||||
|
||||
void set(const CharType_t * const *new_array) {
|
||||
void set(const CharType_t * const *new_array)
|
||||
{
|
||||
if (new_array == array)
|
||||
return;
|
||||
|
||||
|
@ -414,10 +442,12 @@ class null_terminated_array_t {
|
|||
this->free();
|
||||
|
||||
/* Copy the new one */
|
||||
if (new_array) {
|
||||
if (new_array)
|
||||
{
|
||||
size_t i, count = count_not_null(new_array);
|
||||
this->array = new CharType_t * [count + 1];
|
||||
for (i=0; i < count; i++) {
|
||||
for (i=0; i < count; i++)
|
||||
{
|
||||
size_t len = count_not_null(new_array[i]);
|
||||
this->array[i] = new CharType_t [1 + len];
|
||||
std::copy(new_array[i], new_array[i] + len, this->array[i]);
|
||||
|
@ -427,12 +457,20 @@ class null_terminated_array_t {
|
|||
}
|
||||
}
|
||||
|
||||
CharType_t **get() { return array; }
|
||||
const CharType_t * const *get() const { return array; }
|
||||
CharType_t **get()
|
||||
{
|
||||
return array;
|
||||
}
|
||||
const CharType_t * const *get() const
|
||||
{
|
||||
return array;
|
||||
}
|
||||
|
||||
string_list_t to_list() const {
|
||||
string_list_t to_list() const
|
||||
{
|
||||
string_list_t lst;
|
||||
if (array != NULL) {
|
||||
if (array != NULL)
|
||||
{
|
||||
size_t count = this->size();
|
||||
lst.reserve(count);
|
||||
lst.insert(lst.end(), array, array + count);
|
||||
|
@ -445,7 +483,8 @@ class null_terminated_array_t {
|
|||
null_terminated_array_t<char> convert_wide_array_to_narrow(const null_terminated_array_t<wchar_t> &arr);
|
||||
|
||||
/* Helper class to cache a narrow version of a wcstring in a malloc'd buffer, so that we can read it after fork() */
|
||||
class narrow_string_rep_t {
|
||||
class narrow_string_rep_t
|
||||
{
|
||||
private:
|
||||
const char *str;
|
||||
|
||||
|
@ -454,18 +493,21 @@ class narrow_string_rep_t {
|
|||
narrow_string_rep_t(const narrow_string_rep_t &x);
|
||||
|
||||
public:
|
||||
~narrow_string_rep_t() {
|
||||
~narrow_string_rep_t()
|
||||
{
|
||||
free((void *)str);
|
||||
}
|
||||
|
||||
narrow_string_rep_t() : str(NULL) {}
|
||||
|
||||
void set(const wcstring &s) {
|
||||
void set(const wcstring &s)
|
||||
{
|
||||
free((void *)str);
|
||||
str = wcs2str(s.c_str());
|
||||
}
|
||||
|
||||
const char *get() const {
|
||||
const char *get() const
|
||||
{
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
@ -473,7 +515,8 @@ class narrow_string_rep_t {
|
|||
bool is_forked_child();
|
||||
|
||||
/* Basic scoped lock class */
|
||||
class scoped_lock {
|
||||
class scoped_lock
|
||||
{
|
||||
pthread_mutex_t *lock_obj;
|
||||
bool locked;
|
||||
|
||||
|
@ -489,7 +532,8 @@ public:
|
|||
};
|
||||
|
||||
/* Wrapper around wcstok */
|
||||
class wcstokenizer {
|
||||
class wcstokenizer
|
||||
{
|
||||
wchar_t *buffer, *str, *state;
|
||||
const wcstring sep;
|
||||
|
||||
|
|
109
complete.cpp
109
complete.cpp
|
@ -205,13 +205,18 @@ public:
|
|||
};
|
||||
|
||||
/** Set of all completion entries */
|
||||
struct completion_entry_set_comparer {
|
||||
struct completion_entry_set_comparer
|
||||
{
|
||||
/** Comparison for std::set */
|
||||
bool operator()(completion_entry_t *p1, completion_entry_t *p2) const {
|
||||
bool operator()(completion_entry_t *p1, completion_entry_t *p2) const
|
||||
{
|
||||
/* Paths always come last for no particular reason */
|
||||
if (p1->cmd_is_path != p2->cmd_is_path) {
|
||||
if (p1->cmd_is_path != p2->cmd_is_path)
|
||||
{
|
||||
return p1->cmd_is_path < p2->cmd_is_path;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return p1->cmd < p2->cmd;
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +225,8 @@ typedef std::set<completion_entry_t *, completion_entry_set_comparer> completion
|
|||
static completion_entry_set_t completion_set;
|
||||
|
||||
// Comparison function to sort completions by their order field
|
||||
static bool compare_completions_by_order(const completion_entry_t *p1, const completion_entry_t *p2) {
|
||||
static bool compare_completions_by_order(const completion_entry_t *p1, const completion_entry_t *p2)
|
||||
{
|
||||
return p1->order < p2->order;
|
||||
}
|
||||
|
||||
|
@ -235,22 +241,26 @@ static pthread_mutex_t completion_lock = PTHREAD_MUTEX_INITIALIZER;
|
|||
static pthread_mutex_t completion_entry_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
|
||||
void completion_entry_t::add_option(const complete_entry_opt_t &opt) {
|
||||
void completion_entry_t::add_option(const complete_entry_opt_t &opt)
|
||||
{
|
||||
ASSERT_IS_LOCKED(completion_entry_lock);
|
||||
options.push_front(opt);
|
||||
}
|
||||
|
||||
const option_list_t &completion_entry_t::get_options() const {
|
||||
const option_list_t &completion_entry_t::get_options() const
|
||||
{
|
||||
ASSERT_IS_LOCKED(completion_entry_lock);
|
||||
return options;
|
||||
}
|
||||
|
||||
wcstring &completion_entry_t::get_short_opt_str() {
|
||||
wcstring &completion_entry_t::get_short_opt_str()
|
||||
{
|
||||
ASSERT_IS_LOCKED(completion_entry_lock);
|
||||
return short_opt_str;
|
||||
}
|
||||
|
||||
const wcstring &completion_entry_t::get_short_opt_str() const {
|
||||
const wcstring &completion_entry_t::get_short_opt_str() const
|
||||
{
|
||||
ASSERT_IS_LOCKED(completion_entry_lock);
|
||||
return short_opt_str;
|
||||
}
|
||||
|
@ -302,7 +312,8 @@ wcstring_list_t completions_to_wcstring_list( const std::vector<completion_t> &l
|
|||
{
|
||||
wcstring_list_t strings;
|
||||
strings.reserve(list.size());
|
||||
for (std::vector<completion_t>::const_iterator iter = list.begin(); iter != list.end(); ++iter) {
|
||||
for (std::vector<completion_t>::const_iterator iter = list.begin(); iter != list.end(); ++iter)
|
||||
{
|
||||
strings.push_back(iter->completion);
|
||||
}
|
||||
return strings;
|
||||
|
@ -314,7 +325,8 @@ void sort_completions( std::vector<completion_t> &completions)
|
|||
}
|
||||
|
||||
/** Class representing an attempt to compute completions */
|
||||
class completer_t {
|
||||
class completer_t
|
||||
{
|
||||
const complete_type_t type;
|
||||
const wcstring initial_cmd;
|
||||
std::vector<completion_t> completions;
|
||||
|
@ -331,8 +343,14 @@ class completer_t {
|
|||
{
|
||||
}
|
||||
|
||||
bool empty() const { return completions.empty(); }
|
||||
const std::vector<completion_t> &get_completions(void) { return completions; }
|
||||
bool empty() const
|
||||
{
|
||||
return completions.empty();
|
||||
}
|
||||
const std::vector<completion_t> &get_completions(void)
|
||||
{
|
||||
return completions;
|
||||
}
|
||||
|
||||
bool try_complete_variable(const wcstring &str);
|
||||
bool try_complete_user(const wcstring &str);
|
||||
|
@ -368,7 +386,8 @@ class completer_t {
|
|||
std::vector<completion_t> &possible_comp,
|
||||
complete_flags_t flags);
|
||||
|
||||
expand_flags_t expand_flags() const {
|
||||
expand_flags_t expand_flags() const
|
||||
{
|
||||
/* Never do command substitution in autosuggestions. Sadly, we also can't yet do job expansion because it's not thread safe. */
|
||||
expand_flags_t result = 0;
|
||||
if (type == COMPLETE_AUTOSUGGEST)
|
||||
|
@ -376,7 +395,8 @@ class completer_t {
|
|||
return result;
|
||||
}
|
||||
|
||||
void get_commands_to_load(wcstring_list_t *lst) {
|
||||
void get_commands_to_load(wcstring_list_t *lst)
|
||||
{
|
||||
if (lst)
|
||||
lst->insert(lst->end(), commands_to_load.begin(), commands_to_load.end());
|
||||
}
|
||||
|
@ -384,7 +404,8 @@ class completer_t {
|
|||
};
|
||||
|
||||
/* Autoloader for completions */
|
||||
class completion_autoload_t : public autoload_t {
|
||||
class completion_autoload_t : public autoload_t
|
||||
{
|
||||
public:
|
||||
completion_autoload_t();
|
||||
virtual void command_removed(const wcstring &cmd);
|
||||
|
@ -400,7 +421,8 @@ completion_autoload_t::completion_autoload_t() : autoload_t(L"fish_complete_path
|
|||
}
|
||||
|
||||
/** Callback when an autoloaded completion is removed */
|
||||
void completion_autoload_t::command_removed(const wcstring &cmd) {
|
||||
void completion_autoload_t::command_removed(const wcstring &cmd)
|
||||
{
|
||||
complete_remove(cmd.c_str(), COMMAND, 0, 0);
|
||||
}
|
||||
|
||||
|
@ -438,11 +460,14 @@ bool completer_t::condition_test( const wcstring &condition )
|
|||
|
||||
bool test_res;
|
||||
condition_cache_t::iterator cached_entry = condition_cache.find(condition);
|
||||
if (cached_entry == condition_cache.end()) {
|
||||
if (cached_entry == condition_cache.end())
|
||||
{
|
||||
/* Compute new value and reinsert it */
|
||||
test_res = (0 == exec_subshell(condition));
|
||||
condition_cache[condition] = test_res;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Use the old value */
|
||||
test_res = cached_entry->second;
|
||||
}
|
||||
|
@ -457,7 +482,8 @@ static completion_entry_t *complete_find_exact_entry( const wcstring &cmd, const
|
|||
completion_entry_t *result = NULL;
|
||||
completion_entry_t tmp_entry(cmd, cmd_is_path, L"", false);
|
||||
completion_entry_set_t::iterator iter = completion_set.find(&tmp_entry);
|
||||
if (iter != completion_set.end()) {
|
||||
if (iter != completion_set.end())
|
||||
{
|
||||
result = *iter;
|
||||
}
|
||||
return result;
|
||||
|
@ -501,7 +527,8 @@ void complete_add(const wchar_t *cmd,
|
|||
const wchar_t *condition,
|
||||
const wchar_t *comp,
|
||||
const wchar_t *desc,
|
||||
complete_flags_t flags) {
|
||||
complete_flags_t flags)
|
||||
{
|
||||
CHECK(cmd,);
|
||||
|
||||
/* Lock the lock that allows us to edit the completion entry list */
|
||||
|
@ -603,10 +630,12 @@ void complete_remove( const wchar_t *cmd,
|
|||
|
||||
completion_entry_t tmp_entry(cmd, cmd_is_path, L"", false);
|
||||
completion_entry_set_t::iterator iter = completion_set.find(&tmp_entry);
|
||||
if (iter != completion_set.end()) {
|
||||
if (iter != completion_set.end())
|
||||
{
|
||||
completion_entry_t *entry = *iter;
|
||||
bool delete_it = entry->remove_option(short_opt, long_opt);
|
||||
if (delete_it) {
|
||||
if (delete_it)
|
||||
{
|
||||
/* Delete this entry */
|
||||
completion_set.erase(iter);
|
||||
delete entry;
|
||||
|
@ -615,7 +644,8 @@ void complete_remove( const wchar_t *cmd,
|
|||
}
|
||||
|
||||
/* Formats an error string by prepending the prefix and then appending the str in single quotes */
|
||||
static wcstring format_error(const wchar_t *prefix, const wcstring &str) {
|
||||
static wcstring format_error(const wchar_t *prefix, const wcstring &str)
|
||||
{
|
||||
wcstring result = prefix;
|
||||
result.push_back(L'\'');
|
||||
result.append(str);
|
||||
|
@ -626,17 +656,22 @@ static wcstring format_error(const wchar_t *prefix, const wcstring &str) {
|
|||
/**
|
||||
Find the full path and commandname from a command string 'str'.
|
||||
*/
|
||||
static void parse_cmd_string(const wcstring &str, wcstring &path, wcstring &cmd) {
|
||||
if (! path_get_path(str, &path)) {
|
||||
static void parse_cmd_string(const wcstring &str, wcstring &path, wcstring &cmd)
|
||||
{
|
||||
if (! path_get_path(str, &path))
|
||||
{
|
||||
/** Use the empty string as the 'path' for commands that can not be found. */
|
||||
path = L"";
|
||||
}
|
||||
|
||||
/* Make sure the path is not included in the command */
|
||||
size_t last_slash = str.find_last_of(L'/');
|
||||
if (last_slash != wcstring::npos) {
|
||||
if (last_slash != wcstring::npos)
|
||||
{
|
||||
cmd = str.substr(last_slash + 1);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd = str;
|
||||
}
|
||||
}
|
||||
|
@ -712,7 +747,8 @@ int complete_is_valid_option( const wcstring &str,
|
|||
/*
|
||||
Make sure completions are loaded for the specified command
|
||||
*/
|
||||
if (allow_autoload) {
|
||||
if (allow_autoload)
|
||||
{
|
||||
complete_load(cmd, false);
|
||||
}
|
||||
|
||||
|
@ -1032,7 +1068,8 @@ static wcstring complete_function_desc( const wcstring &fn )
|
|||
{
|
||||
wcstring result;
|
||||
bool has_description = function_get_desc(fn, &result);
|
||||
if (! has_description) {
|
||||
if (! has_description)
|
||||
{
|
||||
function_get_definition(fn, &result);
|
||||
}
|
||||
return result;
|
||||
|
@ -1070,7 +1107,8 @@ void completer_t::complete_cmd( const wcstring &str_cmd, bool use_function, bool
|
|||
|
||||
if (expand_string(str_cmd, this->completions, ACCEPT_INCOMPLETE | EXECUTABLES_ONLY | this->expand_flags()) != EXPAND_ERROR)
|
||||
{
|
||||
if (wants_description) {
|
||||
if (wants_description)
|
||||
{
|
||||
this->complete_cmd_desc(str_cmd);
|
||||
}
|
||||
}
|
||||
|
@ -1128,7 +1166,8 @@ void completer_t::complete_cmd( const wcstring &str_cmd, bool use_function, bool
|
|||
{
|
||||
//function_get_names( &possible_comp, cmd[0] == L'_' );
|
||||
wcstring_list_t names = function_get_names(str_cmd.at(0) == L'_');
|
||||
for (size_t i=0; i < names.size(); i++) {
|
||||
for (size_t i=0; i < names.size(); i++)
|
||||
{
|
||||
possible_comp.push_back(completion_t(names.at(i)));
|
||||
}
|
||||
|
||||
|
@ -1282,7 +1321,8 @@ void complete_load( const wcstring &name, bool reload )
|
|||
previous option popt. Insert results into comp_out. Return 0 if file
|
||||
completion should be disabled, 1 otherwise.
|
||||
*/
|
||||
struct local_options_t {
|
||||
struct local_options_t
|
||||
{
|
||||
wcstring short_opt_str;
|
||||
option_list_t options;
|
||||
};
|
||||
|
@ -1554,7 +1594,8 @@ void completer_t::complete_param_expand( const wcstring &sstr, bool do_file)
|
|||
|
||||
void completer_t::debug_print_completions()
|
||||
{
|
||||
for (size_t i=0; i < completions.size(); i++) {
|
||||
for (size_t i=0; i < completions.size(); i++)
|
||||
{
|
||||
printf("- Completion: %ls\n", completions.at(i).completion.c_str());
|
||||
}
|
||||
}
|
||||
|
|
11
complete.h
11
complete.h
|
@ -67,7 +67,8 @@
|
|||
*/
|
||||
#define PROG_COMPLETE_SEP L'\t'
|
||||
|
||||
enum {
|
||||
enum
|
||||
{
|
||||
/**
|
||||
Do not insert space afterwards if this is the only completion. (The
|
||||
default is to try insert a space)
|
||||
|
@ -134,7 +135,10 @@ public:
|
|||
*/
|
||||
int flags;
|
||||
|
||||
bool is_case_insensitive() const { return !! (flags & COMPLETE_NO_CASE); }
|
||||
bool is_case_insensitive() const
|
||||
{
|
||||
return !!(flags & COMPLETE_NO_CASE);
|
||||
}
|
||||
|
||||
/* Construction. Note: defining these so that they are not inlined reduces the executable size. */
|
||||
completion_t(const wcstring &comp, const wcstring &desc = L"", int flags_val = 0);
|
||||
|
@ -147,7 +151,8 @@ public:
|
|||
bool operator != (const completion_t& rhs) const;
|
||||
};
|
||||
|
||||
enum complete_type_t {
|
||||
enum complete_type_t
|
||||
{
|
||||
COMPLETE_DEFAULT,
|
||||
COMPLETE_AUTOSUGGEST
|
||||
};
|
||||
|
|
84
env.cpp
84
env.cpp
|
@ -134,7 +134,8 @@ struct env_node_t
|
|||
env_node_t() : new_scope(0), exportv(0), next(NULL) { }
|
||||
};
|
||||
|
||||
class variable_entry_t {
|
||||
class variable_entry_t
|
||||
{
|
||||
bool exportv; /**< Whether the variable should be exported */
|
||||
wcstring value; /**< Value of the variable */
|
||||
};
|
||||
|
@ -277,9 +278,12 @@ static mode_t get_umask()
|
|||
}
|
||||
|
||||
/** Checks if the specified variable is a locale variable */
|
||||
static bool var_is_locale(const wcstring &key) {
|
||||
for (size_t i=0; locale_variable[i]; i++) {
|
||||
if (key == locale_variable[i]) {
|
||||
static bool var_is_locale(const wcstring &key)
|
||||
{
|
||||
for (size_t i=0; locale_variable[i]; i++)
|
||||
{
|
||||
if (key == locale_variable[i])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -360,13 +364,19 @@ static void handle_locale()
|
|||
|
||||
|
||||
/** React to modifying hte given variable */
|
||||
static void react_to_variable_change(const wcstring &key) {
|
||||
if(var_is_locale(key)){
|
||||
static void react_to_variable_change(const wcstring &key)
|
||||
{
|
||||
if (var_is_locale(key))
|
||||
{
|
||||
handle_locale();
|
||||
} else if (key == L"fish_term256") {
|
||||
}
|
||||
else if (key == L"fish_term256")
|
||||
{
|
||||
update_fish_term256();
|
||||
reader_react_to_color_change();
|
||||
} else if (string_prefixes_string(L"fish_color_", key)) {
|
||||
}
|
||||
else if (string_prefixes_string(L"fish_color_", key))
|
||||
{
|
||||
reader_react_to_color_change();
|
||||
}
|
||||
}
|
||||
|
@ -529,10 +539,14 @@ static void env_set_defaults()
|
|||
}
|
||||
|
||||
// Some variables should not be arrays. This used to be handled by a startup script, but we'd like to get down to 0 forks for startup, so handle it here.
|
||||
static bool variable_can_be_array(const wchar_t *key) {
|
||||
if (! wcscmp(key, L"DISPLAY")) {
|
||||
static bool variable_can_be_array(const wchar_t *key)
|
||||
{
|
||||
if (! wcscmp(key, L"DISPLAY"))
|
||||
{
|
||||
return false;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -548,7 +562,8 @@ void env_init(const struct config_paths_t *paths /* or NULL */)
|
|||
env_read_only variables can not be altered directly by the user
|
||||
*/
|
||||
|
||||
const wchar_t * const ro_keys[] = {
|
||||
const wchar_t * const ro_keys[] =
|
||||
{
|
||||
L"status",
|
||||
L"history",
|
||||
L"version",
|
||||
|
@ -559,7 +574,8 @@ void env_init(const struct config_paths_t *paths /* or NULL */)
|
|||
L"SHLVL",
|
||||
L"FISH_VERSION",
|
||||
};
|
||||
for (size_t i=0; i < sizeof ro_keys / sizeof *ro_keys; i++) {
|
||||
for (size_t i=0; i < sizeof ro_keys / sizeof *ro_keys; i++)
|
||||
{
|
||||
env_read_only.insert(ro_keys[i]);
|
||||
}
|
||||
|
||||
|
@ -615,9 +631,12 @@ void env_init(const struct config_paths_t *paths /* or NULL */)
|
|||
val++;
|
||||
|
||||
//fwprintf( stderr, L"Set $%ls to %ls\n", key, val );
|
||||
if (variable_can_be_array(val)) {
|
||||
for (size_t i=0; val[i] != L'\0'; i++) {
|
||||
if( val[i] == L':' ) {
|
||||
if (variable_can_be_array(val))
|
||||
{
|
||||
for (size_t i=0; val[i] != L'\0'; i++)
|
||||
{
|
||||
if (val[i] == L':')
|
||||
{
|
||||
val[i] = ARRAY_SEP;
|
||||
}
|
||||
}
|
||||
|
@ -712,7 +731,8 @@ void env_destroy()
|
|||
|
||||
|
||||
var_table_t::iterator iter;
|
||||
for (iter = global->begin(); iter != global->end(); ++iter) {
|
||||
for (iter = global->begin(); iter != global->end(); ++iter)
|
||||
{
|
||||
var_entry_t *entry = iter->second;
|
||||
if (entry->exportv)
|
||||
{
|
||||
|
@ -767,7 +787,8 @@ int env_set(const wcstring &key, const wchar_t *val, int var_mode)
|
|||
/* Canoncalize our path; if it changes, recurse and try again. */
|
||||
wcstring val_canonical = val;
|
||||
path_make_canonical(val_canonical);
|
||||
if (val != val_canonical) {
|
||||
if (val != val_canonical)
|
||||
{
|
||||
return env_set(key, val_canonical.c_str(), var_mode);
|
||||
}
|
||||
}
|
||||
|
@ -1069,13 +1090,15 @@ int env_remove( const wcstring &key, int var_mode )
|
|||
return !erased;
|
||||
}
|
||||
|
||||
env_var_t env_var_t::missing_var(void) {
|
||||
env_var_t env_var_t::missing_var(void)
|
||||
{
|
||||
env_var_t result(L"");
|
||||
result.is_missing = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
const wchar_t *env_var_t::c_str(void) const {
|
||||
const wchar_t *env_var_t::c_str(void) const
|
||||
{
|
||||
assert(! is_missing);
|
||||
return wcstring::c_str();
|
||||
}
|
||||
|
@ -1089,7 +1112,8 @@ env_var_t env_get_string( const wcstring &key )
|
|||
env_var_t result;
|
||||
|
||||
history_t *history = reader_get_history();
|
||||
if (! history) {
|
||||
if (! history)
|
||||
{
|
||||
history = &history_t::history_with_name(L"fish");
|
||||
}
|
||||
if (history)
|
||||
|
@ -1416,7 +1440,8 @@ wcstring_list_t env_get_names( int flags )
|
|||
if (show_global)
|
||||
{
|
||||
add_key_to_string_set(global_env->env, names);
|
||||
if( get_names_show_unexported ) {
|
||||
if (get_names_show_unexported)
|
||||
{
|
||||
result.insert(result.end(), env_electric.begin(), env_electric.end());
|
||||
}
|
||||
|
||||
|
@ -1498,7 +1523,8 @@ static void export_func(const std::map<wcstring, wcstring> &envs, std::vector<st
|
|||
}
|
||||
}
|
||||
|
||||
static void update_export_array_if_necessary(bool recalc) {
|
||||
static void update_export_array_if_necessary(bool recalc)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
if (recalc && ! get_proc_had_barrier())
|
||||
{
|
||||
|
@ -1522,7 +1548,8 @@ static void update_export_array_if_necessary(bool recalc) {
|
|||
const wcstring &key = uni.at(i);
|
||||
const wchar_t *val = env_universal_get(key.c_str());
|
||||
|
||||
if (wcscmp( val, ENV_NULL)) {
|
||||
if (wcscmp(val, ENV_NULL))
|
||||
{
|
||||
// Note that std::map::insert does NOT overwrite a value already in the map,
|
||||
// which we depend on here
|
||||
vals.insert(std::pair<wcstring, wcstring>(key, val));
|
||||
|
@ -1555,10 +1582,12 @@ env_vars_snapshot_t::env_vars_snapshot_t(const wchar_t * const *keys)
|
|||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
wcstring key;
|
||||
for (size_t i=0; keys[i]; i++) {
|
||||
for (size_t i=0; keys[i]; i++)
|
||||
{
|
||||
key.assign(keys[i]);
|
||||
const env_var_t val = env_get_string(key);
|
||||
if (! val.missing()) {
|
||||
if (! val.missing())
|
||||
{
|
||||
vars[key] = val;
|
||||
}
|
||||
}
|
||||
|
@ -1585,7 +1614,8 @@ env_var_t env_vars_snapshot_t::get(const wcstring &key) const
|
|||
{
|
||||
return env_get_string(key);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
std::map<wcstring, wcstring>::const_iterator iter = vars.find(key);
|
||||
return (iter == vars.end() ? env_var_t::missing_var() : env_var_t(iter->second));
|
||||
}
|
||||
|
|
25
env.h
25
env.h
|
@ -46,7 +46,8 @@
|
|||
/**
|
||||
Error code for trying to alter read-only variable
|
||||
*/
|
||||
enum{
|
||||
enum
|
||||
{
|
||||
ENV_PERM = 1,
|
||||
ENV_INVALID
|
||||
}
|
||||
|
@ -102,7 +103,8 @@ int env_set(const wcstring &key, const wchar_t *val, int mode);
|
|||
*/
|
||||
//const wchar_t *env_get( const wchar_t *key );
|
||||
|
||||
class env_var_t : public wcstring {
|
||||
class env_var_t : public wcstring
|
||||
{
|
||||
private:
|
||||
bool is_missing;
|
||||
public:
|
||||
|
@ -111,16 +113,24 @@ public:
|
|||
env_var_t(const wcstring & x) : wcstring(x), is_missing(false) { }
|
||||
env_var_t(const wchar_t *x) : wcstring(x), is_missing(false) { }
|
||||
env_var_t() : wcstring(L""), is_missing(false) { }
|
||||
bool missing(void) const { return is_missing; }
|
||||
bool missing_or_empty(void) const { return missing() || empty(); }
|
||||
bool missing(void) const
|
||||
{
|
||||
return is_missing;
|
||||
}
|
||||
bool missing_or_empty(void) const
|
||||
{
|
||||
return missing() || empty();
|
||||
}
|
||||
const wchar_t *c_str(void) const;
|
||||
env_var_t &operator=(const env_var_t &s) {
|
||||
env_var_t &operator=(const env_var_t &s)
|
||||
{
|
||||
is_missing = s.is_missing;
|
||||
wcstring::operator=(s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const env_var_t &s) const {
|
||||
bool operator==(const env_var_t &s) const
|
||||
{
|
||||
if (is_missing && s.is_missing)
|
||||
return true;
|
||||
else if (s.is_missing || s.is_missing)
|
||||
|
@ -181,7 +191,8 @@ wcstring_list_t env_get_names( int flags );
|
|||
*/
|
||||
int env_set_pwd();
|
||||
|
||||
class env_vars_snapshot_t {
|
||||
class env_vars_snapshot_t
|
||||
{
|
||||
std::map<wcstring, wcstring> vars;
|
||||
bool is_current() const;
|
||||
|
||||
|
|
|
@ -181,8 +181,14 @@ static size_t hack_iconv(iconv_t cd, const char * const* inbuf, size_t *inbytesl
|
|||
struct sloppy_char
|
||||
{
|
||||
const char * const * t;
|
||||
operator char** () const { return (char **)t; }
|
||||
operator const char** () const { return (const char**)t; }
|
||||
operator char** () const
|
||||
{
|
||||
return (char **)t;
|
||||
}
|
||||
operator const char** () const
|
||||
{
|
||||
return (const char**)t;
|
||||
}
|
||||
} slop_inbuf = {inbuf};
|
||||
|
||||
return iconv(cd, slop_inbuf, inbytesleft, outbuf, outbytesleft);
|
||||
|
|
15
event.cpp
15
event.cpp
|
@ -225,9 +225,11 @@ wcstring event_get_desc( const event_t *e )
|
|||
}
|
||||
|
||||
#if 0
|
||||
static void show_all_handlers(void) {
|
||||
static void show_all_handlers(void)
|
||||
{
|
||||
puts("event handlers:");
|
||||
for (event_list_t::const_iterator iter = events.begin(); iter != events.end(); ++iter) {
|
||||
for (event_list_t::const_iterator iter = events.begin(); iter != events.end(); ++iter)
|
||||
{
|
||||
const event_t *foo = *iter;
|
||||
wcstring tmp = event_get_desc(foo);
|
||||
printf(" handler now %ls\n", tmp.c_str());
|
||||
|
@ -637,19 +639,22 @@ void event_fire_generic_internal(const wchar_t *name, ...)
|
|||
ev.arguments.reset(NULL);
|
||||
}
|
||||
|
||||
event_t event_t::signal_event(int sig) {
|
||||
event_t event_t::signal_event(int sig)
|
||||
{
|
||||
event_t event(EVENT_SIGNAL);
|
||||
event.param1.signal = sig;
|
||||
return event;
|
||||
}
|
||||
|
||||
event_t event_t::variable_event(const wcstring &str) {
|
||||
event_t event_t::variable_event(const wcstring &str)
|
||||
{
|
||||
event_t event(EVENT_VARIABLE);
|
||||
event.str_param1 = str;
|
||||
return event;
|
||||
}
|
||||
|
||||
event_t event_t::generic_event(const wcstring &str) {
|
||||
event_t event_t::generic_event(const wcstring &str)
|
||||
{
|
||||
event_t event(EVENT_GENERIC);
|
||||
event.str_param1 = str;
|
||||
return event;
|
||||
|
|
3
event.h
3
event.h
|
@ -60,7 +60,8 @@ struct event_t
|
|||
pid: Process id for process-type events. Use EVENT_ANY_PID to match any pid.
|
||||
job_id: Job id for EVENT_JOB_ID type events
|
||||
*/
|
||||
union {
|
||||
union
|
||||
{
|
||||
int signal;
|
||||
int job_id;
|
||||
pid_t pid;
|
||||
|
|
75
exec.cpp
75
exec.cpp
|
@ -116,7 +116,8 @@ void exec_close( int fd )
|
|||
}
|
||||
|
||||
/* Maybe remove this from our set of open fds */
|
||||
if ((size_t)fd < open_fds.size()) {
|
||||
if ((size_t)fd < open_fds.size())
|
||||
{
|
||||
open_fds[fd] = false;
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +138,8 @@ int exec_pipe( int fd[2])
|
|||
debug(4, L"Created pipe using fds %d and %d", fd[0], fd[1]);
|
||||
|
||||
int max_fd = std::max(fd[0], fd[1]);
|
||||
if (max_fd >= 0 && open_fds.size() <= (size_t)max_fd) {
|
||||
if (max_fd >= 0 && open_fds.size() <= (size_t)max_fd)
|
||||
{
|
||||
open_fds.resize(max_fd + 1, false);
|
||||
}
|
||||
open_fds.at(fd[0]) = true;
|
||||
|
@ -182,8 +184,10 @@ static bool use_fd_in_pipe(int fd, const io_chain_t &io_chain )
|
|||
void close_unused_internal_pipes(const io_chain_t &io)
|
||||
{
|
||||
/* A call to exec_close will modify open_fds, so be careful how we walk */
|
||||
for (size_t i=0; i < open_fds.size(); i++) {
|
||||
if (open_fds[i]) {
|
||||
for (size_t i=0; i < open_fds.size(); i++)
|
||||
{
|
||||
if (open_fds[i])
|
||||
{
|
||||
int fd = (int)i;
|
||||
if (!use_fd_in_pipe(fd, io))
|
||||
{
|
||||
|
@ -197,8 +201,10 @@ void close_unused_internal_pipes( const io_chain_t &io )
|
|||
|
||||
void get_unused_internal_pipes(std::vector<int> &fds, const io_chain_t &io)
|
||||
{
|
||||
for (size_t i=0; i < open_fds.size(); i++) {
|
||||
if (open_fds[i]) {
|
||||
for (size_t i=0; i < open_fds.size(); i++)
|
||||
{
|
||||
if (open_fds[i])
|
||||
{
|
||||
int fd = (int)i;
|
||||
if (!use_fd_in_pipe(fd, io))
|
||||
{
|
||||
|
@ -232,11 +238,16 @@ char *get_interpreter( const char *command, char *interpreter, size_t buff_size
|
|||
interpreter[idx++] = '\0';
|
||||
close(fd);
|
||||
}
|
||||
if (strncmp(interpreter, "#! /", 4) == 0) {
|
||||
if (strncmp(interpreter, "#! /", 4) == 0)
|
||||
{
|
||||
return interpreter + 3;
|
||||
} else if (strncmp(interpreter, "#!/", 3) == 0) {
|
||||
}
|
||||
else if (strncmp(interpreter, "#!/", 3) == 0)
|
||||
{
|
||||
return interpreter + 2;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
@ -278,7 +289,8 @@ static void safe_launch_process( process_t *p, const char *actual_cmd, char **ar
|
|||
char sh_command[] = "/bin/sh";
|
||||
char *argv2[128];
|
||||
argv2[0] = sh_command;
|
||||
for (size_t i=1; i < sizeof argv2 / sizeof *argv2; i++) {
|
||||
for (size_t i=1; i < sizeof argv2 / sizeof *argv2; i++)
|
||||
{
|
||||
argv2[i] = argv[i-1];
|
||||
if (argv2[i] == NULL)
|
||||
break;
|
||||
|
@ -324,9 +336,11 @@ static int has_fd( const io_chain_t &d, int fd )
|
|||
used by a transmogrified IO_FILE redirection are freed, since the
|
||||
original chain may still be needed.
|
||||
*/
|
||||
static void io_cleanup_chains(io_chain_t &chains, const std::vector<int> &opened_fds) {
|
||||
static void io_cleanup_chains(io_chain_t &chains, const std::vector<int> &opened_fds)
|
||||
{
|
||||
/* Close all the fds */
|
||||
for (size_t idx = 0; idx < opened_fds.size(); idx++) {
|
||||
for (size_t idx = 0; idx < opened_fds.size(); idx++)
|
||||
{
|
||||
close(opened_fds.at(idx));
|
||||
}
|
||||
|
||||
|
@ -343,12 +357,14 @@ static void io_cleanup_chains(io_chain_t &chains, const std::vector<int> &opened
|
|||
|
||||
\return the transmogrified chain on sucess, or 0 on failiure
|
||||
*/
|
||||
static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t &out_chain, std::vector<int> &out_opened_fds) {
|
||||
static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t &out_chain, std::vector<int> &out_opened_fds)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
assert(out_chain.empty());
|
||||
|
||||
/* Just to be clear what we do for an empty chain */
|
||||
if (in_chain.empty()) {
|
||||
if (in_chain.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -418,17 +434,21 @@ static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t &out_chain, s
|
|||
result_chain.push_back(out);
|
||||
|
||||
/* But don't go any further if we failed */
|
||||
if (! success) {
|
||||
if (! success)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now either return success, or clean up */
|
||||
if (success) {
|
||||
if (success)
|
||||
{
|
||||
/* Yay */
|
||||
out_chain.swap(result_chain);
|
||||
out_opened_fds.swap(opened_fds);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No dice - clean up */
|
||||
io_cleanup_chains(result_chain, opened_fds);
|
||||
}
|
||||
|
@ -677,7 +697,8 @@ void exec( parser_t &parser, job_t *j )
|
|||
if (needs_keepalive)
|
||||
{
|
||||
/* Call fork. No need to wait for threads since our use is confined and simple. */
|
||||
if (g_log_forks) {
|
||||
if (g_log_forks)
|
||||
{
|
||||
printf("fork #%d: Executing keepalive fork for '%ls'\n", g_fork_count, j->command_wcstr());
|
||||
}
|
||||
keepalive.pid = execute_fork(false);
|
||||
|
@ -1036,7 +1057,8 @@ void exec( parser_t &parser, job_t *j )
|
|||
if (io_buffer->out_buffer_size() > 0)
|
||||
{
|
||||
/* We don't have to drain threads here because our child process is simple */
|
||||
if (g_log_forks) {
|
||||
if (g_log_forks)
|
||||
{
|
||||
printf("Executing fork for internal block or function for '%ls'\n", p->argv0());
|
||||
}
|
||||
pid = execute_fork(false);
|
||||
|
@ -1088,7 +1110,8 @@ void exec( parser_t &parser, job_t *j )
|
|||
size_t count = input_redirect->out_buffer_size();
|
||||
|
||||
/* We don't have to drain threads here because our child process is simple */
|
||||
if (g_log_forks) {
|
||||
if (g_log_forks)
|
||||
{
|
||||
printf("fork #%d: Executing fork for internal buffer for '%ls'\n", g_fork_count, p->argv0() ? p->argv0() : L"(null)");
|
||||
}
|
||||
pid = execute_fork(false);
|
||||
|
@ -1158,9 +1181,11 @@ void exec( parser_t &parser, job_t *j )
|
|||
skip_fork = 1;
|
||||
}
|
||||
|
||||
if (! skip_fork && j->io.empty()) {
|
||||
if (! skip_fork && j->io.empty())
|
||||
{
|
||||
/* PCA for some reason, fish forks a lot, even for basic builtins like echo just to write out their buffers. I'm certain a lot of this is unnecessary, but I am not sure exactly when. If j->io is NULL, then it means there's no pipes or anything, so we can certainly just write out our data. Beyond that, we may be able to do the same if io_get returns 0 for STDOUT_FILENO and STDERR_FILENO. */
|
||||
if (g_log_forks) {
|
||||
if (g_log_forks)
|
||||
{
|
||||
printf("fork #-: Skipping fork for internal builtin for '%ls'\n", p->argv0());
|
||||
}
|
||||
const wcstring &out = get_stdout_buffer(), &err = get_stderr_buffer();
|
||||
|
@ -1204,7 +1229,8 @@ void exec( parser_t &parser, job_t *j )
|
|||
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
if (g_log_forks) {
|
||||
if (g_log_forks)
|
||||
{
|
||||
printf("fork #%d: Executing fork for internal builtin for '%ls'\n", g_fork_count, p->argv0());
|
||||
io_print(io_chain_t(io));
|
||||
}
|
||||
|
@ -1257,7 +1283,8 @@ void exec( parser_t &parser, job_t *j )
|
|||
const char *actual_cmd = actual_cmd_str.c_str();
|
||||
|
||||
const wchar_t *reader_current_filename();
|
||||
if (g_log_forks) {
|
||||
if (g_log_forks)
|
||||
{
|
||||
const wchar_t *file = reader_current_filename();
|
||||
const wchar_t *func = parser_t::principal_parser().is_function();
|
||||
printf("fork #%d: forking for '%s' in '%ls:%ls'\n", g_fork_count, actual_cmd, file ? file : L"", func ? func : L"?");
|
||||
|
|
70
expand.cpp
70
expand.cpp
|
@ -186,7 +186,8 @@ static int is_quotable( const wchar_t *str )
|
|||
|
||||
}
|
||||
|
||||
static int is_quotable(const wcstring &str) {
|
||||
static int is_quotable(const wcstring &str)
|
||||
{
|
||||
return is_quotable(str.c_str());
|
||||
}
|
||||
|
||||
|
@ -294,7 +295,8 @@ static bool match_pid( const wcstring &cmd,
|
|||
|
||||
/* BSD / OS X process completions */
|
||||
|
||||
class process_iterator_t {
|
||||
class process_iterator_t
|
||||
{
|
||||
std::vector<pid_t> pids;
|
||||
size_t idx;
|
||||
|
||||
|
@ -316,12 +318,14 @@ wcstring process_iterator_t::name_for_pid(pid_t pid)
|
|||
mib[1] = KERN_ARGMAX;
|
||||
|
||||
size = sizeof(maxarg);
|
||||
if (sysctl(mib, 2, &maxarg, &size, NULL, 0) == -1) {
|
||||
if (sysctl(mib, 2, &maxarg, &size, NULL, 0) == -1)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
args = (char *)malloc(maxarg);
|
||||
if ( args == NULL ) {
|
||||
if (args == NULL)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -330,7 +334,8 @@ wcstring process_iterator_t::name_for_pid(pid_t pid)
|
|||
mib[2] = pid;
|
||||
|
||||
size = (size_t)maxarg;
|
||||
if ( sysctl(mib, 3, args, &size, NULL, 0) == -1 ) {
|
||||
if (sysctl(mib, 3, args, &size, NULL, 0) == -1)
|
||||
{
|
||||
free(args);
|
||||
return result;;
|
||||
}
|
||||
|
@ -388,7 +393,8 @@ process_iterator_t::process_iterator_t() : idx(0)
|
|||
|
||||
result = NULL;
|
||||
done = false;
|
||||
do {
|
||||
do
|
||||
{
|
||||
assert(result == NULL);
|
||||
|
||||
// Call sysctl with a NULL buffer.
|
||||
|
@ -397,16 +403,19 @@ process_iterator_t::process_iterator_t() : idx(0)
|
|||
err = sysctl((int *) name, (sizeof(name) / sizeof(*name)) - 1,
|
||||
NULL, &length,
|
||||
NULL, 0);
|
||||
if (err == -1) {
|
||||
if (err == -1)
|
||||
{
|
||||
err = errno;
|
||||
}
|
||||
|
||||
// Allocate an appropriately sized buffer based on the results
|
||||
// from the previous call.
|
||||
|
||||
if (err == 0) {
|
||||
if (err == 0)
|
||||
{
|
||||
result = (struct kinfo_proc *)malloc(length);
|
||||
if (result == NULL) {
|
||||
if (result == NULL)
|
||||
{
|
||||
err = ENOMEM;
|
||||
}
|
||||
}
|
||||
|
@ -414,23 +423,29 @@ process_iterator_t::process_iterator_t() : idx(0)
|
|||
// Call sysctl again with the new buffer. If we get an ENOMEM
|
||||
// error, toss away our buffer and start again.
|
||||
|
||||
if (err == 0) {
|
||||
if (err == 0)
|
||||
{
|
||||
err = sysctl((int *) name, (sizeof(name) / sizeof(*name)) - 1,
|
||||
result, &length,
|
||||
NULL, 0);
|
||||
if (err == -1) {
|
||||
if (err == -1)
|
||||
{
|
||||
err = errno;
|
||||
}
|
||||
if (err == 0) {
|
||||
if (err == 0)
|
||||
{
|
||||
done = true;
|
||||
} else if (err == ENOMEM) {
|
||||
}
|
||||
else if (err == ENOMEM)
|
||||
{
|
||||
assert(result != NULL);
|
||||
free(result);
|
||||
result = NULL;
|
||||
err = 0;
|
||||
}
|
||||
}
|
||||
} while (err == 0 && ! done);
|
||||
}
|
||||
while (err == 0 && ! done);
|
||||
|
||||
// Clean up and establish post conditions.
|
||||
if (err == 0 && result != NULL)
|
||||
|
@ -446,7 +461,8 @@ process_iterator_t::process_iterator_t() : idx(0)
|
|||
#else
|
||||
|
||||
/* /proc style process completions */
|
||||
class process_iterator_t {
|
||||
class process_iterator_t
|
||||
{
|
||||
DIR *dir;
|
||||
|
||||
public:
|
||||
|
@ -911,7 +927,8 @@ static int parse_slice( const wchar_t *in, wchar_t **end_ptr, std::vector<long>
|
|||
pos = end-in;
|
||||
while (in[pos]==INTERNAL_SEPARATOR)
|
||||
pos++;
|
||||
if ( in[pos]==L'.' && in[pos+1]==L'.' ){
|
||||
if (in[pos]==L'.' && in[pos+1]==L'.')
|
||||
{
|
||||
pos+=2;
|
||||
while (in[pos]==INTERNAL_SEPARATOR)
|
||||
pos++;
|
||||
|
@ -926,7 +943,8 @@ static int parse_slice( const wchar_t *in, wchar_t **end_ptr, std::vector<long>
|
|||
long i2 = tmp1>-1 ? tmp1 : size+tmp1+1;
|
||||
// debug( 0, L"Push range idx %d %d", i1, i2 );
|
||||
short direction = i2<i1 ? -1 : 1 ;
|
||||
for (long jjj = i1; jjj*direction <= i2*direction; jjj+=direction) {
|
||||
for (long jjj = i1; jjj*direction <= i2*direction; jjj+=direction)
|
||||
{
|
||||
// debug(0, L"Expand range [subst]: %i\n", jjj);
|
||||
idx.push_back(jjj);
|
||||
}
|
||||
|
@ -966,7 +984,8 @@ static int parse_slice( const wchar_t *in, wchar_t **end_ptr, std::vector<long>
|
|||
*/
|
||||
static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::vector<completion_t> &out, long last_idx);
|
||||
|
||||
static int expand_variables2( parser_t &parser, const wcstring &instr, std::vector<completion_t> &out, long last_idx ) {
|
||||
static int expand_variables2(parser_t &parser, const wcstring &instr, std::vector<completion_t> &out, long last_idx)
|
||||
{
|
||||
wchar_t *in = wcsdup(instr.c_str());
|
||||
int result = expand_variables_internal(parser, in, out, last_idx);
|
||||
free(in);
|
||||
|
@ -1592,8 +1611,10 @@ int expand_string( const wcstring &input, std::vector<completion_t> &output, exp
|
|||
|
||||
if (EXPAND_SKIP_VARIABLES & flags)
|
||||
{
|
||||
for (size_t i=0; i < next.size(); i++) {
|
||||
if (next.at(i) == VARIABLE_EXPAND) {
|
||||
for (size_t i=0; i < next.size(); i++)
|
||||
{
|
||||
if (next.at(i) == VARIABLE_EXPAND)
|
||||
{
|
||||
next[i] = L'$';
|
||||
}
|
||||
}
|
||||
|
@ -1752,7 +1773,8 @@ int expand_string( const wcstring &input, std::vector<completion_t> &output, exp
|
|||
return res;
|
||||
}
|
||||
|
||||
bool expand_one(wcstring &string, expand_flags_t flags) {
|
||||
bool expand_one(wcstring &string, expand_flags_t flags)
|
||||
{
|
||||
std::vector<completion_t> completions;
|
||||
bool result = false;
|
||||
|
||||
|
@ -1761,8 +1783,10 @@ bool expand_one(wcstring &string, expand_flags_t flags) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (expand_string(string, completions, flags)) {
|
||||
if (completions.size() == 1) {
|
||||
if (expand_string(string, completions, flags))
|
||||
{
|
||||
if (completions.size() == 1)
|
||||
{
|
||||
string = completions.at(0).completion;
|
||||
result = true;
|
||||
}
|
||||
|
|
3
expand.h
3
expand.h
|
@ -21,7 +21,8 @@
|
|||
#include "common.h"
|
||||
#include <list>
|
||||
|
||||
enum {
|
||||
enum
|
||||
{
|
||||
/** Flag specifying that cmdsubst expansion should be skipped */
|
||||
EXPAND_SKIP_CMDSUBST = 1 << 0,
|
||||
|
||||
|
|
12
fallback.cpp
12
fallback.cpp
|
@ -1322,19 +1322,22 @@ int fish_wcswidth(const wchar_t *str, size_t n)
|
|||
|
||||
#include <wchar.h>
|
||||
|
||||
struct interval {
|
||||
struct interval
|
||||
{
|
||||
int first;
|
||||
int last;
|
||||
};
|
||||
|
||||
/* auxiliary function for binary search in interval table */
|
||||
static int bisearch(wchar_t ucs, const struct interval *table, int max) {
|
||||
static int bisearch(wchar_t ucs, const struct interval *table, int max)
|
||||
{
|
||||
int min = 0;
|
||||
int mid;
|
||||
|
||||
if (ucs < table[0].first || ucs > table[max].last)
|
||||
return 0;
|
||||
while (max >= min) {
|
||||
while (max >= min)
|
||||
{
|
||||
mid = (min + max) / 2;
|
||||
if (ucs > table[mid].last)
|
||||
min = mid + 1;
|
||||
|
@ -1384,7 +1387,8 @@ static int mk_wcwidth(wchar_t ucs)
|
|||
{
|
||||
/* sorted list of non-overlapping intervals of non-spacing characters */
|
||||
/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
|
||||
static const struct interval combining[] = {
|
||||
static const struct interval combining[] =
|
||||
{
|
||||
{ 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
|
||||
{ 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
|
||||
{ 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
|
||||
|
|
|
@ -163,9 +163,12 @@ static int indent( wcstring &out, const wcstring &in, int flags )
|
|||
case TOK_PIPE:
|
||||
{
|
||||
out.append(L" ");
|
||||
if ( last[0] == '2' && !last[1] ) {
|
||||
if (last[0] == '2' && !last[1])
|
||||
{
|
||||
out.append(L"^");
|
||||
} else if ( last[0] != '1' || last[1] ) {
|
||||
}
|
||||
else if (last[0] != '1' || last[1])
|
||||
{
|
||||
out.append(last);
|
||||
out.append(L">");
|
||||
}
|
||||
|
@ -177,9 +180,12 @@ static int indent( wcstring &out, const wcstring &in, int flags )
|
|||
case TOK_REDIRECT_OUT:
|
||||
{
|
||||
out.append(L" ");
|
||||
if ( wcscmp( last, L"2" ) == 0 ) {
|
||||
if (wcscmp(last, L"2") == 0)
|
||||
{
|
||||
out.append(L"^");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wcscmp(last, L"1") != 0)
|
||||
out.append(last);
|
||||
out.append(L"> ");
|
||||
|
@ -190,9 +196,12 @@ static int indent( wcstring &out, const wcstring &in, int flags )
|
|||
case TOK_REDIRECT_APPEND:
|
||||
{
|
||||
out.append(L" ");
|
||||
if ( wcscmp( last, L"2" ) == 0 ) {
|
||||
if (wcscmp(last, L"2") == 0)
|
||||
{
|
||||
out.append(L"^^");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wcscmp(last, L"1") != 0)
|
||||
out.append(last);
|
||||
out.append(L">> ");
|
||||
|
|
|
@ -352,7 +352,8 @@ static int pager_buffered_writer( char c)
|
|||
*/
|
||||
static void pager_flush()
|
||||
{
|
||||
if (! pager_buffer.empty()) {
|
||||
if (! pager_buffer.empty())
|
||||
{
|
||||
write_loop(1, & pager_buffer.at(0), pager_buffer.size() * sizeof(char));
|
||||
pager_buffer.clear();
|
||||
}
|
||||
|
@ -1093,9 +1094,12 @@ static void init( int mangle_descriptors, int out )
|
|||
/* Infer term256 support */
|
||||
char *fish_term256 = getenv("fish_term256");
|
||||
bool support_term256;
|
||||
if (fish_term256) {
|
||||
if (fish_term256)
|
||||
{
|
||||
support_term256 = from_string<bool>(fish_term256);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
support_term256 = term && strstr(term, "256color");
|
||||
}
|
||||
output_set_supports_term256(support_term256);
|
||||
|
|
235
fish_tests.cpp
235
fish_tests.cpp
|
@ -159,9 +159,15 @@ static void test_escape()
|
|||
}
|
||||
}
|
||||
|
||||
static void test_format(void) {
|
||||
static void test_format(void)
|
||||
{
|
||||
say(L"Testing formatting functions");
|
||||
struct { unsigned long long val; const char *expected; } tests[] = {
|
||||
struct
|
||||
{
|
||||
unsigned long long val;
|
||||
const char *expected;
|
||||
} tests[] =
|
||||
{
|
||||
{ 0, "empty" },
|
||||
{ 1, "1B" },
|
||||
{ 2, "2B" },
|
||||
|
@ -170,13 +176,15 @@ static void test_format(void) {
|
|||
{ 4322911, "4.1MB" }
|
||||
};
|
||||
size_t i;
|
||||
for (i=0; i < sizeof tests / sizeof *tests; i++) {
|
||||
for (i=0; i < sizeof tests / sizeof *tests; i++)
|
||||
{
|
||||
char buff[128];
|
||||
format_size_safe(buff, tests[i].val);
|
||||
assert(! strcmp(buff, tests[i].expected));
|
||||
}
|
||||
|
||||
for (int j=-129; j <= 129; j++) {
|
||||
for (int j=-129; j <= 129; j++)
|
||||
{
|
||||
char buff1[128], buff2[128];
|
||||
format_long_safe(buff1, j);
|
||||
sprintf(buff2, "%d", j);
|
||||
|
@ -340,46 +348,60 @@ static void test_tok()
|
|||
}
|
||||
}
|
||||
|
||||
static int test_fork_helper(void *unused) {
|
||||
static int test_fork_helper(void *unused)
|
||||
{
|
||||
size_t i;
|
||||
for (i=0; i < 100000; i++) {
|
||||
for (i=0; i < 100000; i++)
|
||||
{
|
||||
delete [](new char[4 * 1024 * 1024]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_fork(void) {
|
||||
static void test_fork(void)
|
||||
{
|
||||
return; // Test is disabled until I can force it to fail
|
||||
say(L"Testing fork");
|
||||
size_t i, max = 100;
|
||||
for (i=0; i < 100; i++) {
|
||||
for (i=0; i < 100; i++)
|
||||
{
|
||||
printf("%lu / %lu\n", i+1, max);
|
||||
/* Do something horrible to try to trigger an error */
|
||||
#define THREAD_COUNT 8
|
||||
#define FORK_COUNT 200
|
||||
#define FORK_LOOP_COUNT 16
|
||||
signal_block();
|
||||
for (size_t i=0; i < THREAD_COUNT; i++) {
|
||||
for (size_t i=0; i < THREAD_COUNT; i++)
|
||||
{
|
||||
iothread_perform<void>(test_fork_helper, NULL, NULL);
|
||||
}
|
||||
for (size_t q = 0; q < FORK_LOOP_COUNT; q++) {
|
||||
for (size_t q = 0; q < FORK_LOOP_COUNT; q++)
|
||||
{
|
||||
pid_t pids[FORK_COUNT];
|
||||
for (size_t i=0; i < FORK_COUNT; i++) {
|
||||
for (size_t i=0; i < FORK_COUNT; i++)
|
||||
{
|
||||
pid_t pid = execute_fork(false);
|
||||
if (pid > 0) {
|
||||
if (pid > 0)
|
||||
{
|
||||
/* Parent */
|
||||
pids[i] = pid;
|
||||
} else if (pid == 0) {
|
||||
}
|
||||
else if (pid == 0)
|
||||
{
|
||||
/* Child */
|
||||
new char[4 * 1024 * 1024];
|
||||
exit_without_destructors(0);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
perror("fork");
|
||||
}
|
||||
}
|
||||
for (size_t i=0; i < FORK_COUNT; i++) {
|
||||
for (size_t i=0; i < FORK_COUNT; i++)
|
||||
{
|
||||
int status = 0;
|
||||
if (pids[i] != waitpid(pids[i], &status, 0)) {
|
||||
if (pids[i] != waitpid(pids[i], &status, 0))
|
||||
{
|
||||
perror("waitpid");
|
||||
assert(0);
|
||||
}
|
||||
|
@ -466,30 +488,35 @@ static void test_parser()
|
|||
}
|
||||
}
|
||||
|
||||
class lru_node_test_t : public lru_node_t {
|
||||
class lru_node_test_t : public lru_node_t
|
||||
{
|
||||
public:
|
||||
lru_node_test_t(const wcstring &tmp) : lru_node_t(tmp) { }
|
||||
};
|
||||
|
||||
class test_lru_t : public lru_cache_t<lru_node_test_t> {
|
||||
class test_lru_t : public lru_cache_t<lru_node_test_t>
|
||||
{
|
||||
public:
|
||||
test_lru_t() : lru_cache_t<lru_node_test_t>(16) { }
|
||||
|
||||
std::vector<lru_node_test_t *> evicted_nodes;
|
||||
|
||||
virtual void node_was_evicted(lru_node_test_t *node) {
|
||||
virtual void node_was_evicted(lru_node_test_t *node)
|
||||
{
|
||||
assert(find(evicted_nodes.begin(), evicted_nodes.end(), node) == evicted_nodes.end());
|
||||
evicted_nodes.push_back(node);
|
||||
}
|
||||
};
|
||||
|
||||
static void test_lru(void) {
|
||||
static void test_lru(void)
|
||||
{
|
||||
say(L"Testing LRU cache");
|
||||
|
||||
test_lru_t cache;
|
||||
std::vector<lru_node_test_t *> expected_evicted;
|
||||
size_t total_nodes = 20;
|
||||
for (size_t i=0; i < total_nodes; i++) {
|
||||
for (size_t i=0; i < total_nodes; i++)
|
||||
{
|
||||
assert(cache.size() == std::min(i, (size_t)16));
|
||||
lru_node_test_t *node = new lru_node_test_t(to_string(i));
|
||||
if (i < 4) expected_evicted.push_back(node);
|
||||
|
@ -500,7 +527,8 @@ static void test_lru(void) {
|
|||
assert(cache.evicted_nodes == expected_evicted);
|
||||
cache.evict_all_nodes();
|
||||
assert(cache.evicted_nodes.size() == total_nodes);
|
||||
while (! cache.evicted_nodes.empty()) {
|
||||
while (! cache.evicted_nodes.empty())
|
||||
{
|
||||
lru_node_t *node = cache.evicted_nodes.back();
|
||||
cache.evicted_nodes.pop_back();
|
||||
delete node;
|
||||
|
@ -622,7 +650,8 @@ static void test_path()
|
|||
static void test_is_potential_path()
|
||||
{
|
||||
say(L"Testing is_potential_path");
|
||||
if (system("rm -Rf /tmp/is_potential_path_test/")) {
|
||||
if (system("rm -Rf /tmp/is_potential_path_test/"))
|
||||
{
|
||||
err(L"Failed to remove /tmp/is_potential_path_test/");
|
||||
}
|
||||
|
||||
|
@ -661,12 +690,14 @@ static void test_is_potential_path()
|
|||
|
||||
/** Test the 'test' builtin */
|
||||
int builtin_test(parser_t &parser, wchar_t **argv);
|
||||
static bool run_test_test(int expected, wcstring_list_t &lst) {
|
||||
static bool run_test_test(int expected, wcstring_list_t &lst)
|
||||
{
|
||||
parser_t parser(PARSER_TYPE_GENERAL, true);
|
||||
size_t i, count = lst.size();
|
||||
wchar_t **argv = new wchar_t *[count+2];
|
||||
argv[0] = (wchar_t *)L"test";
|
||||
for (i=0; i < count; i++) {
|
||||
for (i=0; i < count; i++)
|
||||
{
|
||||
argv[i+1] = (wchar_t *)lst.at(i).c_str();
|
||||
}
|
||||
argv[i+1] = NULL;
|
||||
|
@ -675,7 +706,8 @@ static bool run_test_test(int expected, wcstring_list_t &lst) {
|
|||
return expected == result;
|
||||
}
|
||||
|
||||
static bool run_test_test(int expected, const wcstring &str) {
|
||||
static bool run_test_test(int expected, const wcstring &str)
|
||||
{
|
||||
using namespace std;
|
||||
wcstring_list_t lst;
|
||||
|
||||
|
@ -686,7 +718,8 @@ static bool run_test_test(int expected, const wcstring &str) {
|
|||
return run_test_test(expected, lst);
|
||||
}
|
||||
|
||||
static void test_test() {
|
||||
static void test_test()
|
||||
{
|
||||
say(L"Testing test builtin");
|
||||
|
||||
assert(run_test_test(0, L"5 -ne 6"));
|
||||
|
@ -778,7 +811,8 @@ static void perform_one_autosuggestion_test(const wcstring &command, const wcstr
|
|||
}
|
||||
|
||||
/* Testing test_autosuggest_suggest_special, in particular for properly handling quotes and backslashes */
|
||||
static void test_autosuggest_suggest_special() {
|
||||
static void test_autosuggest_suggest_special()
|
||||
{
|
||||
if (system("mkdir -p '/tmp/autosuggest_test/0foobar'")) err(L"mkdir failed");
|
||||
if (system("mkdir -p '/tmp/autosuggest_test/1foo bar'")) err(L"mkdir failed");
|
||||
if (system("mkdir -p '/tmp/autosuggest_test/2foo bar'")) err(L"mkdir failed");
|
||||
|
@ -906,29 +940,35 @@ void perf_complete()
|
|||
|
||||
}
|
||||
|
||||
static void test_history_matches(history_search_t &search, size_t matches) {
|
||||
static void test_history_matches(history_search_t &search, size_t matches)
|
||||
{
|
||||
size_t i;
|
||||
for (i=0; i < matches; i++) {
|
||||
for (i=0; i < matches; i++)
|
||||
{
|
||||
assert(search.go_backwards());
|
||||
wcstring item = search.current_string();
|
||||
}
|
||||
assert(! search.go_backwards());
|
||||
|
||||
for (i=1; i < matches; i++) {
|
||||
for (i=1; i < matches; i++)
|
||||
{
|
||||
assert(search.go_forwards());
|
||||
}
|
||||
assert(! search.go_forwards());
|
||||
}
|
||||
|
||||
static bool history_contains(history_t *history, const wcstring &txt) {
|
||||
static bool history_contains(history_t *history, const wcstring &txt)
|
||||
{
|
||||
bool result = false;
|
||||
size_t i;
|
||||
for (i=1; ; i++) {
|
||||
for (i=1; ; i++)
|
||||
{
|
||||
history_item_t item = history->item_at_index(i);
|
||||
if (item.empty())
|
||||
break;
|
||||
|
||||
if (item.str() == txt) {
|
||||
if (item.str() == txt)
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
|
@ -936,24 +976,28 @@ static bool history_contains(history_t *history, const wcstring &txt) {
|
|||
return result;
|
||||
}
|
||||
|
||||
class history_tests_t {
|
||||
class history_tests_t
|
||||
{
|
||||
public:
|
||||
static void test_history(void);
|
||||
static void test_history_merge(void);
|
||||
static void test_history_formats(void);
|
||||
};
|
||||
|
||||
static wcstring random_string(void) {
|
||||
static wcstring random_string(void)
|
||||
{
|
||||
wcstring result;
|
||||
size_t max = 1 + rand() % 32;
|
||||
while (max--) {
|
||||
while (max--)
|
||||
{
|
||||
wchar_t c = 1 + rand()%ESCAPE_TEST_CHAR;
|
||||
result.push_back(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void history_tests_t::test_history(void) {
|
||||
void history_tests_t::test_history(void)
|
||||
{
|
||||
say(L"Testing history");
|
||||
|
||||
history_t &history = history_t::history_with_name(L"test_history");
|
||||
|
@ -981,7 +1025,8 @@ void history_tests_t::test_history(void) {
|
|||
std::vector<history_item_t> before, after;
|
||||
history.clear();
|
||||
size_t i, max = 100;
|
||||
for (i=1; i <= max; i++) {
|
||||
for (i=1; i <= max; i++)
|
||||
{
|
||||
|
||||
/* Generate a value */
|
||||
wcstring value = wcstring(L"test item ") + to_string(i);
|
||||
|
@ -993,7 +1038,8 @@ void history_tests_t::test_history(void) {
|
|||
/* Generate some paths */
|
||||
path_list_t paths;
|
||||
size_t count = rand() % 6;
|
||||
while (count--) {
|
||||
while (count--)
|
||||
{
|
||||
paths.push_back(random_string());
|
||||
}
|
||||
|
||||
|
@ -1005,13 +1051,15 @@ void history_tests_t::test_history(void) {
|
|||
history.save();
|
||||
|
||||
/* Read items back in reverse order and ensure they're the same */
|
||||
for (i=100; i >= 1; i--) {
|
||||
for (i=100; i >= 1; i--)
|
||||
{
|
||||
history_item_t item = history.item_at_index(i);
|
||||
assert(! item.empty());
|
||||
after.push_back(item);
|
||||
}
|
||||
assert(before.size() == after.size());
|
||||
for (size_t i=0; i < before.size(); i++) {
|
||||
for (size_t i=0; i < before.size(); i++)
|
||||
{
|
||||
const history_item_t &bef = before.at(i), &aft = after.at(i);
|
||||
assert(bef.contents == aft.contents);
|
||||
assert(bef.creation_timestamp == aft.creation_timestamp);
|
||||
|
@ -1023,14 +1071,18 @@ void history_tests_t::test_history(void) {
|
|||
}
|
||||
|
||||
// wait until the next second
|
||||
static void time_barrier(void) {
|
||||
static void time_barrier(void)
|
||||
{
|
||||
time_t start = time(NULL);
|
||||
do {
|
||||
do
|
||||
{
|
||||
usleep(1000);
|
||||
} while (time(NULL) == start);
|
||||
}
|
||||
while (time(NULL) == start);
|
||||
}
|
||||
|
||||
void history_tests_t::test_history_merge(void) {
|
||||
void history_tests_t::test_history_merge(void)
|
||||
{
|
||||
// In a single fish process, only one history is allowed to exist with the given name
|
||||
// But it's common to have multiple history instances with the same name active in different processes,
|
||||
// e.g. when you have multiple shells open.
|
||||
|
@ -1042,7 +1094,8 @@ void history_tests_t::test_history_merge(void) {
|
|||
wcstring texts[count] = {L"History 1", L"History 2", L"History 3"};
|
||||
|
||||
/* Make sure history is clear */
|
||||
for (size_t i=0; i < count; i++) {
|
||||
for (size_t i=0; i < count; i++)
|
||||
{
|
||||
hists[i]->clear();
|
||||
}
|
||||
|
||||
|
@ -1050,18 +1103,22 @@ void history_tests_t::test_history_merge(void) {
|
|||
time_barrier();
|
||||
|
||||
/* Add a different item to each */
|
||||
for (size_t i=0; i < count; i++) {
|
||||
for (size_t i=0; i < count; i++)
|
||||
{
|
||||
hists[i]->add(texts[i]);
|
||||
}
|
||||
|
||||
/* Save them */
|
||||
for (size_t i=0; i < count; i++) {
|
||||
for (size_t i=0; i < count; i++)
|
||||
{
|
||||
hists[i]->save();
|
||||
}
|
||||
|
||||
/* Make sure each history contains what it ought to, but they have not leaked into each other */
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
for (size_t j=0; j < count; j++) {
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
for (size_t j=0; j < count; j++)
|
||||
{
|
||||
bool does_contain = history_contains(hists[i], texts[j]);
|
||||
bool should_contain = (i == j);
|
||||
assert(should_contain == does_contain);
|
||||
|
@ -1071,22 +1128,26 @@ void history_tests_t::test_history_merge(void) {
|
|||
/* Make a new history. It should contain everything. The time_barrier() is so that the timestamp is newer, since we only pick up items whose timestamp is before the birth stamp. */
|
||||
time_barrier();
|
||||
history_t *everything = new history_t(name);
|
||||
for (size_t i=0; i < count; i++) {
|
||||
for (size_t i=0; i < count; i++)
|
||||
{
|
||||
assert(history_contains(everything, texts[i]));
|
||||
}
|
||||
|
||||
/* Clean up */
|
||||
for (size_t i=0; i < 3; i++) {
|
||||
for (size_t i=0; i < 3; i++)
|
||||
{
|
||||
delete hists[i];
|
||||
}
|
||||
everything->clear();
|
||||
delete everything; //not as scary as it looks
|
||||
}
|
||||
|
||||
static bool install_sample_history(const wchar_t *name) {
|
||||
static bool install_sample_history(const wchar_t *name)
|
||||
{
|
||||
char command[512];
|
||||
snprintf(command, sizeof command, "cp tests/%ls ~/.config/fish/%ls_history", name, name);
|
||||
if (system(command)) {
|
||||
if (system(command))
|
||||
{
|
||||
err(L"Failed to copy sample history");
|
||||
return false;
|
||||
}
|
||||
|
@ -1094,26 +1155,34 @@ static bool install_sample_history(const wchar_t *name) {
|
|||
}
|
||||
|
||||
/* Indicates whether the history is equal to the given null-terminated array of strings. */
|
||||
static bool history_equals(history_t &hist, const wchar_t * const *strings) {
|
||||
static bool history_equals(history_t &hist, const wchar_t * const *strings)
|
||||
{
|
||||
/* Count our expected items */
|
||||
size_t expected_count = 0;
|
||||
while (strings[expected_count]) {
|
||||
while (strings[expected_count])
|
||||
{
|
||||
expected_count++;
|
||||
}
|
||||
|
||||
/* Ensure the contents are the same */
|
||||
size_t history_idx = 1;
|
||||
size_t array_idx = 0;
|
||||
for (;;) {
|
||||
for (;;)
|
||||
{
|
||||
const wchar_t *expected = strings[array_idx];
|
||||
history_item_t item = hist.item_at_index(history_idx);
|
||||
if (expected == NULL) {
|
||||
if (! item.empty()) {
|
||||
if (expected == NULL)
|
||||
{
|
||||
if (! item.empty())
|
||||
{
|
||||
err(L"Expected empty item at history index %lu", history_idx);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
if (item.str() != expected) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.str() != expected)
|
||||
{
|
||||
err(L"Expected '%ls', found '%ls' at index %lu", expected, item.str().c_str(), history_idx);
|
||||
}
|
||||
}
|
||||
|
@ -1124,17 +1193,22 @@ static bool history_equals(history_t &hist, const wchar_t * const *strings) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void history_tests_t::test_history_formats(void) {
|
||||
void history_tests_t::test_history_formats(void)
|
||||
{
|
||||
const wchar_t *name;
|
||||
|
||||
// Test inferring and reading legacy and bash history formats
|
||||
name = L"history_sample_fish_1_x";
|
||||
say(L"Testing %ls", name);
|
||||
if (! install_sample_history(name)) {
|
||||
if (! install_sample_history(name))
|
||||
{
|
||||
err(L"Couldn't open file tests/%ls", name);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Note: This is backwards from what appears in the file */
|
||||
const wchar_t * const expected[] = {
|
||||
const wchar_t * const expected[] =
|
||||
{
|
||||
L"#def",
|
||||
|
||||
L"echo #abc",
|
||||
|
@ -1151,7 +1225,8 @@ void history_tests_t::test_history_formats(void) {
|
|||
};
|
||||
|
||||
history_t &test_history = history_t::history_with_name(name);
|
||||
if (! history_equals(test_history, expected)) {
|
||||
if (! history_equals(test_history, expected))
|
||||
{
|
||||
err(L"test_history_formats failed for %ls\n", name);
|
||||
}
|
||||
test_history.clear();
|
||||
|
@ -1159,10 +1234,14 @@ void history_tests_t::test_history_formats(void) {
|
|||
|
||||
name = L"history_sample_fish_2_0";
|
||||
say(L"Testing %ls", name);
|
||||
if (! install_sample_history(name)) {
|
||||
if (! install_sample_history(name))
|
||||
{
|
||||
err(L"Couldn't open file tests/%ls", name);
|
||||
} else {
|
||||
const wchar_t * const expected[] = {
|
||||
}
|
||||
else
|
||||
{
|
||||
const wchar_t * const expected[] =
|
||||
{
|
||||
L"echo this has\\\nbackslashes",
|
||||
|
||||
L"function foo\n"
|
||||
|
@ -1175,7 +1254,8 @@ void history_tests_t::test_history_formats(void) {
|
|||
};
|
||||
|
||||
history_t &test_history = history_t::history_with_name(name);
|
||||
if (! history_equals(test_history, expected)) {
|
||||
if (! history_equals(test_history, expected))
|
||||
{
|
||||
err(L"test_history_formats failed for %ls\n", name);
|
||||
}
|
||||
test_history.clear();
|
||||
|
@ -1183,11 +1263,15 @@ void history_tests_t::test_history_formats(void) {
|
|||
|
||||
say(L"Testing bash import");
|
||||
FILE *f = fopen("tests/history_sample_bash", "r");
|
||||
if (! f) {
|
||||
if (! f)
|
||||
{
|
||||
err(L"Couldn't open file tests/history_sample_bash");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// It should skip over the export command since that's a bash-ism
|
||||
const wchar_t *expected[] = {
|
||||
const wchar_t *expected[] =
|
||||
{
|
||||
L"echo supsup",
|
||||
|
||||
L"history --help",
|
||||
|
@ -1198,7 +1282,8 @@ void history_tests_t::test_history_formats(void) {
|
|||
};
|
||||
history_t &test_history = history_t::history_with_name(L"bash_import");
|
||||
test_history.populate_from_bash(f);
|
||||
if (! history_equals(test_history, expected)) {
|
||||
if (! history_equals(test_history, expected))
|
||||
{
|
||||
err(L"test_history_formats failed for bash import\n");
|
||||
}
|
||||
test_history.clear();
|
||||
|
|
12
fishd.cpp
12
fishd.cpp
|
@ -375,7 +375,8 @@ static int acquire_lock_file( const char *lockfile, const int timeout, int force
|
|||
}
|
||||
}
|
||||
nanosleep(&pollint, NULL);
|
||||
} while( gettimeofday( &end, NULL ) == 0 );
|
||||
}
|
||||
while (gettimeofday(&end, NULL) == 0);
|
||||
done:
|
||||
/* The linkfile is not needed once the lockfile has been created */
|
||||
(void)unlink(linkfile);
|
||||
|
@ -468,7 +469,8 @@ static int get_socket()
|
|||
wperror(L"fcntl");
|
||||
close(s);
|
||||
doexit = 1;
|
||||
} else if( listen( s, 64 ) == -1 )
|
||||
}
|
||||
else if (listen(s, 64) == -1)
|
||||
{
|
||||
wperror(L"listen");
|
||||
doexit = 1;
|
||||
|
@ -645,7 +647,8 @@ static wcstring fishd_get_config()
|
|||
}
|
||||
}
|
||||
|
||||
if (! done) {
|
||||
if (! done)
|
||||
{
|
||||
/* Bad juju */
|
||||
debug(0, _(L"Unable to create a configuration directory for fish. Your personal settings will not be saved. Please set the $XDG_CONFIG_HOME variable to a directory where the current user has write access."));
|
||||
result.clear();
|
||||
|
@ -854,7 +857,8 @@ int main( int argc, char ** argv )
|
|||
if ((child_socket =
|
||||
accept(sock,
|
||||
(struct sockaddr *)&remote,
|
||||
&t) ) == -1) {
|
||||
&t)) == -1)
|
||||
{
|
||||
wperror(L"accept");
|
||||
exit(1);
|
||||
}
|
||||
|
|
41
function.cpp
41
function.cpp
|
@ -48,7 +48,8 @@ static function_map_t loaded_functions;
|
|||
static pthread_mutex_t functions_lock;
|
||||
|
||||
/* Autoloader for functions */
|
||||
class function_autoload_t : public autoload_t {
|
||||
class function_autoload_t : public autoload_t
|
||||
{
|
||||
public:
|
||||
function_autoload_t();
|
||||
virtual void command_removed(const wcstring &cmd);
|
||||
|
@ -67,7 +68,8 @@ function_autoload_t::function_autoload_t() : autoload_t(L"fish_function_path",
|
|||
static bool function_remove_ignore_autoload(const wcstring &name);
|
||||
|
||||
/** Callback when an autoloaded function is removed */
|
||||
void function_autoload_t::command_removed(const wcstring &cmd) {
|
||||
void function_autoload_t::command_removed(const wcstring &cmd)
|
||||
{
|
||||
function_remove_ignore_autoload(cmd);
|
||||
}
|
||||
|
||||
|
@ -92,7 +94,8 @@ static int load( const wcstring &name )
|
|||
bool was_autoload = is_autoload;
|
||||
int res;
|
||||
function_map_t::iterator iter = loaded_functions.find(name);
|
||||
if( iter != loaded_functions.end() && !iter->second.is_autoload ) {
|
||||
if (iter != loaded_functions.end() && !iter->second.is_autoload)
|
||||
{
|
||||
/* We have a non-autoload version already */
|
||||
return 0;
|
||||
}
|
||||
|
@ -225,7 +228,8 @@ static bool function_remove_ignore_autoload(const wcstring &name)
|
|||
scoped_lock lock(functions_lock);
|
||||
bool erased = (loaded_functions.erase(name) > 0);
|
||||
|
||||
if (erased) {
|
||||
if (erased)
|
||||
{
|
||||
event_t ev(EVENT_ANY);
|
||||
ev.function_name=name;
|
||||
event_remove(&ev);
|
||||
|
@ -246,9 +250,12 @@ static const function_info_t *function_get(const wcstring &name)
|
|||
// We need a way to correctly check if a lock is locked (or better yet, make our lock non-recursive)
|
||||
//ASSERT_IS_LOCKED(functions_lock);
|
||||
function_map_t::iterator iter = loaded_functions.find(name);
|
||||
if (iter == loaded_functions.end()) {
|
||||
if (iter == loaded_functions.end())
|
||||
{
|
||||
return NULL;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return &iter->second;
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +264,8 @@ bool function_get_definition(const wcstring &name, wcstring *out_definition)
|
|||
{
|
||||
scoped_lock lock(functions_lock);
|
||||
const function_info_t *func = function_get(name);
|
||||
if (func && out_definition) {
|
||||
if (func && out_definition)
|
||||
{
|
||||
out_definition->assign(func->definition);
|
||||
}
|
||||
return func != NULL;
|
||||
|
@ -283,10 +291,13 @@ bool function_get_desc(const wcstring &name, wcstring *out_desc)
|
|||
/* Empty length string goes to NULL */
|
||||
scoped_lock lock(functions_lock);
|
||||
const function_info_t *func = function_get(name);
|
||||
if (out_desc && func && ! func->description.empty()) {
|
||||
if (out_desc && func && ! func->description.empty())
|
||||
{
|
||||
out_desc->assign(_(func->description.c_str()));
|
||||
return true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -296,7 +307,8 @@ void function_set_desc(const wcstring &name, const wcstring &desc)
|
|||
load(name);
|
||||
scoped_lock lock(functions_lock);
|
||||
function_map_t::iterator iter = loaded_functions.find(name);
|
||||
if (iter != loaded_functions.end()) {
|
||||
if (iter != loaded_functions.end())
|
||||
{
|
||||
iter->second.description = desc;
|
||||
}
|
||||
}
|
||||
|
@ -306,7 +318,8 @@ bool function_copy(const wcstring &name, const wcstring &new_name)
|
|||
bool result = false;
|
||||
scoped_lock lock(functions_lock);
|
||||
function_map_t::const_iterator iter = loaded_functions.find(name);
|
||||
if (iter != loaded_functions.end()) {
|
||||
if (iter != loaded_functions.end())
|
||||
{
|
||||
// This new instance of the function shouldn't be tied to the definition file of the original, so pass NULL filename, etc.
|
||||
const function_map_t::value_type new_pair(new_name, function_info_t(iter->second, NULL, 0, false));
|
||||
loaded_functions.insert(new_pair);
|
||||
|
@ -322,11 +335,13 @@ wcstring_list_t function_get_names(int get_hidden)
|
|||
autoload_names(names, get_hidden);
|
||||
|
||||
function_map_t::const_iterator iter;
|
||||
for (iter = loaded_functions.begin(); iter != loaded_functions.end(); ++iter) {
|
||||
for (iter = loaded_functions.begin(); iter != loaded_functions.end(); ++iter)
|
||||
{
|
||||
const wcstring &name = iter->first;
|
||||
|
||||
/* Maybe skip hidden */
|
||||
if (! get_hidden) {
|
||||
if (! get_hidden)
|
||||
{
|
||||
if (name.empty() || name.at(0) == L'_') continue;
|
||||
}
|
||||
names.insert(name);
|
||||
|
|
|
@ -55,7 +55,8 @@ struct function_data_t
|
|||
int shadows;
|
||||
};
|
||||
|
||||
class function_info_t {
|
||||
class function_info_t
|
||||
{
|
||||
public:
|
||||
/** Constructs relevant information from the function_data */
|
||||
function_info_t(const function_data_t &data, const wchar_t *filename, int def_offset, bool autoload);
|
||||
|
|
131
highlight.cpp
131
highlight.cpp
|
@ -64,13 +64,15 @@ static const wchar_t * const highlight_var[] =
|
|||
};
|
||||
|
||||
/* If the given path looks like it's relative to the working directory, then prepend that working directory. */
|
||||
static wcstring apply_working_directory(const wcstring &path, const wcstring &working_directory) {
|
||||
static wcstring apply_working_directory(const wcstring &path, const wcstring &working_directory)
|
||||
{
|
||||
if (path.empty() || working_directory.empty())
|
||||
return path;
|
||||
|
||||
/* We're going to make sure that if we want to prepend the wd, that the string has no leading / */
|
||||
bool prepend_wd;
|
||||
switch (path.at(0)) {
|
||||
switch (path.at(0))
|
||||
{
|
||||
case L'/':
|
||||
case L'~':
|
||||
prepend_wd = false;
|
||||
|
@ -80,18 +82,23 @@ static wcstring apply_working_directory(const wcstring &path, const wcstring &wo
|
|||
break;
|
||||
}
|
||||
|
||||
if (! prepend_wd) {
|
||||
if (! prepend_wd)
|
||||
{
|
||||
/* No need to prepend the wd, so just return the path we were given */
|
||||
return path;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Remove up to one ./ */
|
||||
wcstring path_component = path;
|
||||
if (string_prefixes_string(L"./", path_component)) {
|
||||
if (string_prefixes_string(L"./", path_component))
|
||||
{
|
||||
path_component.erase(0, 2);
|
||||
}
|
||||
|
||||
/* Removing leading /s */
|
||||
while (string_prefixes_string(L"/", path_component)) {
|
||||
while (string_prefixes_string(L"/", path_component))
|
||||
{
|
||||
path_component.erase(0, 1);
|
||||
}
|
||||
|
||||
|
@ -111,10 +118,13 @@ bool fs_is_case_insensitive(const wcstring &path, int fd, case_sensitivity_cache
|
|||
#ifdef _PC_CASE_SENSITIVE
|
||||
/* Try the cache first */
|
||||
case_sensitivity_cache_t::iterator cache = case_sensitivity_cache.find(path);
|
||||
if (cache != case_sensitivity_cache.end()) {
|
||||
if (cache != case_sensitivity_cache.end())
|
||||
{
|
||||
/* Use the cached value */
|
||||
result = cache->second;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Ask the system. A -1 value means error (so assume case sensitive), a 1 value means case sensitive, and a 0 value means case insensitive */
|
||||
long ret = fpathconf(fd, _PC_CASE_SENSITIVE);
|
||||
result = (ret == 0);
|
||||
|
@ -185,7 +195,8 @@ bool is_potential_path(const wcstring &const_path, const wcstring_list_t &direct
|
|||
/* Keep a cache of which paths / filesystems are case sensitive */
|
||||
case_sensitivity_cache_t case_sensitivity_cache;
|
||||
|
||||
for (size_t wd_idx = 0; wd_idx < directories.size() && ! result; wd_idx++) {
|
||||
for (size_t wd_idx = 0; wd_idx < directories.size() && ! result; wd_idx++)
|
||||
{
|
||||
const wcstring &wd = directories.at(wd_idx);
|
||||
|
||||
const wcstring abs_path = apply_working_directory(clean_path, wd);
|
||||
|
@ -200,7 +211,8 @@ bool is_potential_path(const wcstring &const_path, const wcstring_list_t &direct
|
|||
if (must_be_full_dir)
|
||||
{
|
||||
struct stat buf;
|
||||
if (0 == wstat(abs_path, &buf) && S_ISDIR(buf.st_mode)) {
|
||||
if (0 == wstat(abs_path, &buf) && S_ISDIR(buf.st_mode))
|
||||
{
|
||||
result = true;
|
||||
/* Return the path suffix, not the whole absolute path */
|
||||
if (out_path)
|
||||
|
@ -220,7 +232,8 @@ bool is_potential_path(const wcstring &const_path, const wcstring_list_t &direct
|
|||
if (out_path)
|
||||
*out_path = clean_path;
|
||||
}
|
||||
else if ((dir = wopendir(dir_name))) {
|
||||
else if ((dir = wopendir(dir_name)))
|
||||
{
|
||||
// We opened the dir_name; look for a string where the base name prefixes it
|
||||
wcstring ent;
|
||||
|
||||
|
@ -234,23 +247,28 @@ bool is_potential_path(const wcstring &const_path, const wcstring_list_t &direct
|
|||
|
||||
/* Determine which function to call to check for prefixes */
|
||||
bool (*prefix_func)(const wcstring &, const wcstring &);
|
||||
if (case_insensitive) {
|
||||
if (case_insensitive)
|
||||
{
|
||||
prefix_func = string_prefixes_string_case_insensitive;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix_func = string_prefixes_string;
|
||||
}
|
||||
|
||||
if (prefix_func(base_name, ent) && (! require_dir || is_dir))
|
||||
{
|
||||
result = true;
|
||||
if (out_path) {
|
||||
if (out_path)
|
||||
{
|
||||
/* We want to return the path in the same "form" as it was given. Take the given path, get its basename. Append that to the output if the basename actually prefixes the path (which it won't if the given path contains no slashes), and isn't a slash (so we don't duplicate slashes). Then append the directory entry. */
|
||||
|
||||
out_path->clear();
|
||||
const wcstring path_base = wdirname(const_path);
|
||||
|
||||
|
||||
if (prefix_func(path_base, const_path)) {
|
||||
if (prefix_func(path_base, const_path))
|
||||
{
|
||||
out_path->append(path_base);
|
||||
if (! string_suffixes_string(L"/", *out_path))
|
||||
out_path->push_back(L'/');
|
||||
|
@ -277,10 +295,13 @@ static bool is_potential_cd_path(const wcstring &path, const wcstring &working_d
|
|||
{
|
||||
wcstring_list_t directories;
|
||||
|
||||
if (string_prefixes_string(L"./", path)) {
|
||||
if (string_prefixes_string(L"./", path))
|
||||
{
|
||||
/* Ignore the CDPATH in this case; just use the working directory */
|
||||
directories.push_back(working_directory);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Get the CDPATH */
|
||||
env_var_t cdpath = env_get_string(L"CDPATH");
|
||||
if (cdpath.missing_or_empty())
|
||||
|
@ -299,7 +320,8 @@ static bool is_potential_cd_path(const wcstring &path, const wcstring &working_d
|
|||
/* Call is_potential_path with all of these directories */
|
||||
bool result = is_potential_path(path, directories, flags | PATH_REQUIRE_DIR, out_path);
|
||||
#if 0
|
||||
if (out_path) {
|
||||
if (out_path)
|
||||
{
|
||||
printf("%ls -> %ls\n", path.c_str(), out_path->c_str());
|
||||
}
|
||||
#endif
|
||||
|
@ -480,7 +502,8 @@ static void highlight_param( const wcstring &buffstr, std::vector<int> &colors,
|
|||
}
|
||||
else
|
||||
{
|
||||
switch( buff[in_pos]){
|
||||
switch (buff[in_pos])
|
||||
{
|
||||
case L'~':
|
||||
case L'%':
|
||||
{
|
||||
|
@ -765,7 +788,8 @@ static bool autosuggest_parse_command(const wcstring &str, wcstring *out_command
|
|||
tok_destroy(&tok);
|
||||
|
||||
/* Remember our command if we have one */
|
||||
if (had_cmd) {
|
||||
if (had_cmd)
|
||||
{
|
||||
if (out_command) out_command->swap(cmd);
|
||||
if (out_arguments) out_arguments->swap(args);
|
||||
if (out_last_arg_pos) *out_last_arg_pos = arg_pos;
|
||||
|
@ -775,7 +799,8 @@ static bool autosuggest_parse_command(const wcstring &str, wcstring *out_command
|
|||
|
||||
|
||||
/* We have to return an escaped string here */
|
||||
bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, wcstring &outSuggestion) {
|
||||
bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, wcstring &outSuggestion)
|
||||
{
|
||||
if (str.empty())
|
||||
return false;
|
||||
|
||||
|
@ -789,7 +814,8 @@ bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_di
|
|||
return false;
|
||||
|
||||
bool result = false;
|
||||
if (parsed_command == L"cd" && ! parsed_arguments.empty()) {
|
||||
if (parsed_command == L"cd" && ! parsed_arguments.empty())
|
||||
{
|
||||
/* We can possibly handle this specially */
|
||||
const wcstring escaped_dir = parsed_arguments.back();
|
||||
wcstring suggested_path;
|
||||
|
@ -821,13 +847,16 @@ bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_di
|
|||
outSuggestion.append(escaped_suggested_path);
|
||||
if (quote != L'\0') outSuggestion.push_back(quote);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Either an error or some other command, so we don't handle it specially */
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool autosuggest_validate_from_history(const history_item_t &item, file_detection_context_t &detector, const wcstring &working_directory, const env_vars_snapshot_t &vars) {
|
||||
bool autosuggest_validate_from_history(const history_item_t &item, file_detection_context_t &detector, const wcstring &working_directory, const env_vars_snapshot_t &vars)
|
||||
{
|
||||
ASSERT_IS_BACKGROUND_THREAD();
|
||||
|
||||
bool handled = false, suggestionOK = false;
|
||||
|
@ -839,24 +868,33 @@ bool autosuggest_validate_from_history(const history_item_t &item, file_detectio
|
|||
if (! autosuggest_parse_command(item.str(), &parsed_command, &parsed_arguments, &parsed_last_arg_pos))
|
||||
return false;
|
||||
|
||||
if (parsed_command == L"cd" && ! parsed_arguments.empty()) {
|
||||
if (parsed_command == L"cd" && ! parsed_arguments.empty())
|
||||
{
|
||||
/* We can possibly handle this specially */
|
||||
wcstring dir = parsed_arguments.back();
|
||||
if (expand_one(dir, EXPAND_SKIP_CMDSUBST))
|
||||
{
|
||||
handled = true;
|
||||
bool is_help = string_prefixes_string(dir, L"--help") || string_prefixes_string(dir, L"-h");
|
||||
if (is_help) {
|
||||
if (is_help)
|
||||
{
|
||||
suggestionOK = false;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
wcstring path;
|
||||
bool can_cd = path_get_cdpath(dir, &path, working_directory.c_str(), vars);
|
||||
if (! can_cd) {
|
||||
if (! can_cd)
|
||||
{
|
||||
suggestionOK = false;
|
||||
} else if (paths_are_same_file(working_directory, path)) {
|
||||
}
|
||||
else if (paths_are_same_file(working_directory, path))
|
||||
{
|
||||
/* Don't suggest the working directory as the path! */
|
||||
suggestionOK = false;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
suggestionOK = true;
|
||||
}
|
||||
}
|
||||
|
@ -864,7 +902,8 @@ bool autosuggest_validate_from_history(const history_item_t &item, file_detectio
|
|||
}
|
||||
|
||||
/* If not handled specially, handle it here */
|
||||
if (! handled) {
|
||||
if (! handled)
|
||||
{
|
||||
bool cmd_ok = false;
|
||||
|
||||
if (path_get_path(parsed_command, NULL))
|
||||
|
@ -876,12 +915,15 @@ bool autosuggest_validate_from_history(const history_item_t &item, file_detectio
|
|||
cmd_ok = true;
|
||||
}
|
||||
|
||||
if (cmd_ok) {
|
||||
if (cmd_ok)
|
||||
{
|
||||
const path_list_t &paths = item.get_required_paths();
|
||||
if (paths.empty()) {
|
||||
if (paths.empty())
|
||||
{
|
||||
suggestionOK= true;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
detector.potential_paths = paths;
|
||||
suggestionOK = detector.paths_are_valid(paths);
|
||||
}
|
||||
|
@ -892,7 +934,8 @@ bool autosuggest_validate_from_history(const history_item_t &item, file_detectio
|
|||
}
|
||||
|
||||
// This function does I/O
|
||||
static void tokenize( const wchar_t * const buff, std::vector<int> &color, const size_t pos, wcstring_list_t *error, const wcstring &working_directory, const env_vars_snapshot_t &vars) {
|
||||
static void tokenize(const wchar_t * const buff, std::vector<int> &color, const size_t pos, wcstring_list_t *error, const wcstring &working_directory, const env_vars_snapshot_t &vars)
|
||||
{
|
||||
ASSERT_IS_BACKGROUND_THREAD();
|
||||
|
||||
wcstring cmd;
|
||||
|
@ -1089,7 +1132,8 @@ static void tokenize( const wchar_t * const buff, std::vector<int> &color, const
|
|||
}
|
||||
else
|
||||
{
|
||||
if( error ) {
|
||||
if (error)
|
||||
{
|
||||
error->push_back(format_string(L"Unknown command \'%ls\'", cmd.c_str()));
|
||||
}
|
||||
color.at(tok_get_pos(&tok)) = (HIGHLIGHT_ERROR);
|
||||
|
@ -1136,7 +1180,8 @@ static void tokenize( const wchar_t * const buff, std::vector<int> &color, const
|
|||
case TOK_STRING:
|
||||
{
|
||||
target_str = tok_last(&tok);
|
||||
if (expand_one(target_str, EXPAND_SKIP_CMDSUBST)) {
|
||||
if (expand_one(target_str, EXPAND_SKIP_CMDSUBST))
|
||||
{
|
||||
target = target_str.c_str();
|
||||
}
|
||||
/*
|
||||
|
@ -1148,7 +1193,8 @@ static void tokenize( const wchar_t * const buff, std::vector<int> &color, const
|
|||
default:
|
||||
{
|
||||
size_t pos = tok_get_pos(&tok);
|
||||
if (pos < color.size()) {
|
||||
if (pos < color.size())
|
||||
{
|
||||
color.at(pos) = HIGHLIGHT_ERROR;
|
||||
}
|
||||
if (error)
|
||||
|
@ -1314,7 +1360,8 @@ void highlight_shell( const wcstring &buff, std::vector<int> &color, size_t pos,
|
|||
|
||||
// highlight the end of the subcommand
|
||||
assert(end >= subbuff);
|
||||
if ((size_t)(end - subbuff) < length) {
|
||||
if ((size_t)(end - subbuff) < length)
|
||||
{
|
||||
color.at(end-subbuff)=HIGHLIGHT_OPERATOR;
|
||||
}
|
||||
|
||||
|
@ -1363,7 +1410,8 @@ void highlight_shell( const wcstring &buff, std::vector<int> &color, size_t pos,
|
|||
for (ptrdiff_t i=tok_begin-cbuff; i < (tok_end-cbuff); i++)
|
||||
{
|
||||
// Don't color HIGHLIGHT_ERROR because it looks dorky. For example, trying to cd into a non-directory would show an underline and also red.
|
||||
if (! (color.at(i) & HIGHLIGHT_ERROR)) {
|
||||
if (!(color.at(i) & HIGHLIGHT_ERROR))
|
||||
{
|
||||
color.at(i) |= HIGHLIGHT_VALID_PATH;
|
||||
}
|
||||
}
|
||||
|
@ -1477,7 +1525,8 @@ static void highlight_universal_internal( const wcstring &buffstr, std::vector<i
|
|||
wchar_t inc_char = c;
|
||||
int level = 0;
|
||||
int match_found=0;
|
||||
for (long i=pos; i >= 0 && (size_t)i < buffstr.size(); i+=step) {
|
||||
for (long i=pos; i >= 0 && (size_t)i < buffstr.size(); i+=step)
|
||||
{
|
||||
const wchar_t test_char = buffstr.at(i);
|
||||
if (test_char == inc_char)
|
||||
level++;
|
||||
|
|
|
@ -122,7 +122,8 @@ bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_di
|
|||
|
||||
This is used only internally to this file, and is exposed only for testing.
|
||||
*/
|
||||
enum {
|
||||
enum
|
||||
{
|
||||
/* The path must be to a directory */
|
||||
PATH_REQUIRE_DIR = 1 << 0,
|
||||
|
||||
|
|
366
history.cpp
366
history.cpp
|
@ -60,20 +60,25 @@ Our history format is intended to be valid YAML. Here it is:
|
|||
/** Whether we print timing information */
|
||||
#define LOG_TIMES 0
|
||||
|
||||
class time_profiler_t {
|
||||
class time_profiler_t
|
||||
{
|
||||
const char *what;
|
||||
double start;
|
||||
public:
|
||||
|
||||
time_profiler_t(const char *w) {
|
||||
if (LOG_TIMES) {
|
||||
time_profiler_t(const char *w)
|
||||
{
|
||||
if (LOG_TIMES)
|
||||
{
|
||||
what = w;
|
||||
start = timef();
|
||||
}
|
||||
}
|
||||
|
||||
~time_profiler_t() {
|
||||
if (LOG_TIMES) {
|
||||
~time_profiler_t()
|
||||
{
|
||||
if (LOG_TIMES)
|
||||
{
|
||||
double end = timef();
|
||||
printf("(LOG_TIMES %s: %02f msec)\n", what, (end - start) * 1000);
|
||||
}
|
||||
|
@ -81,7 +86,8 @@ class time_profiler_t {
|
|||
};
|
||||
|
||||
/* Our LRU cache is used for restricting the amount of history we have, and limiting how long we order it. */
|
||||
class history_lru_node_t : public lru_node_t {
|
||||
class history_lru_node_t : public lru_node_t
|
||||
{
|
||||
public:
|
||||
time_t timestamp;
|
||||
path_list_t required_paths;
|
||||
|
@ -94,11 +100,13 @@ class history_lru_node_t : public lru_node_t {
|
|||
bool write_yaml_to_file(FILE *f) const;
|
||||
};
|
||||
|
||||
class history_lru_cache_t : public lru_cache_t<history_lru_node_t> {
|
||||
class history_lru_cache_t : public lru_cache_t<history_lru_node_t>
|
||||
{
|
||||
protected:
|
||||
|
||||
/* Override to delete evicted nodes */
|
||||
virtual void node_was_evicted(history_lru_node_t *node) {
|
||||
virtual void node_was_evicted(history_lru_node_t *node)
|
||||
{
|
||||
delete node;
|
||||
}
|
||||
|
||||
|
@ -106,17 +114,21 @@ class history_lru_cache_t : public lru_cache_t<history_lru_node_t> {
|
|||
history_lru_cache_t(size_t max) : lru_cache_t<history_lru_node_t>(max) { }
|
||||
|
||||
/* Function to add a history item */
|
||||
void add_item(const history_item_t &item) {
|
||||
void add_item(const history_item_t &item)
|
||||
{
|
||||
/* Skip empty items */
|
||||
if (item.empty())
|
||||
return;
|
||||
|
||||
/* See if it's in the cache. If it is, update the timestamp. If not, we create a new node and add it. Note that calling get_node promotes the node to the front. */
|
||||
history_lru_node_t *node = this->get_node(item.str());
|
||||
if (node != NULL) {
|
||||
if (node != NULL)
|
||||
{
|
||||
node->timestamp = std::max(node->timestamp, item.timestamp());
|
||||
/* What to do about paths here? Let's just ignore them */
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
node = new history_lru_node_t(item);
|
||||
this->add_node(node);
|
||||
}
|
||||
|
@ -139,9 +151,11 @@ static void unescape_yaml(std::string &str);
|
|||
bool history_item_t::merge(const history_item_t &item)
|
||||
{
|
||||
bool result = false;
|
||||
if (this->contents == item.contents) {
|
||||
if (this->contents == item.contents)
|
||||
{
|
||||
this->creation_timestamp = std::max(this->creation_timestamp, item.creation_timestamp);
|
||||
if (this->required_paths.size() < item.required_paths.size()) {
|
||||
if (this->required_paths.size() < item.required_paths.size())
|
||||
{
|
||||
this->required_paths = item.required_paths;
|
||||
}
|
||||
result = true;
|
||||
|
@ -157,8 +171,10 @@ history_item_t::history_item_t(const wcstring &str, time_t when, const path_list
|
|||
{
|
||||
}
|
||||
|
||||
bool history_item_t::matches_search(const wcstring &term, enum history_search_type_t type) const {
|
||||
switch (type) {
|
||||
bool history_item_t::matches_search(const wcstring &term, enum history_search_type_t type) const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
|
||||
case HISTORY_SEARCH_TYPE_CONTAINS:
|
||||
/* We consider equal strings to NOT match a contains search (so that you don't have to see history equal to what you typed). The length check ensures that. */
|
||||
|
@ -175,7 +191,8 @@ bool history_item_t::matches_search(const wcstring &term, enum history_search_ty
|
|||
}
|
||||
|
||||
/* Output our YAML to a file */
|
||||
bool history_lru_node_t::write_yaml_to_file(FILE *f) const {
|
||||
bool history_lru_node_t::write_yaml_to_file(FILE *f) const
|
||||
{
|
||||
std::string cmd = wcs2string(key);
|
||||
escape_yaml(cmd);
|
||||
if (fprintf(f, "- cmd: %s\n", cmd.c_str()) < 0)
|
||||
|
@ -184,11 +201,13 @@ bool history_lru_node_t::write_yaml_to_file(FILE *f) const {
|
|||
if (fprintf(f, " when: %ld\n", (long)timestamp) < 0)
|
||||
return false;
|
||||
|
||||
if (! required_paths.empty()) {
|
||||
if (! required_paths.empty())
|
||||
{
|
||||
if (fputs(" paths:\n", f) < 0)
|
||||
return false;
|
||||
|
||||
for (path_list_t::const_iterator iter = required_paths.begin(); iter != required_paths.end(); ++iter) {
|
||||
for (path_list_t::const_iterator iter = required_paths.begin(); iter != required_paths.end(); ++iter)
|
||||
{
|
||||
std::string path = wcs2string(*iter);
|
||||
escape_yaml(path);
|
||||
|
||||
|
@ -202,7 +221,8 @@ bool history_lru_node_t::write_yaml_to_file(FILE *f) const {
|
|||
|
||||
// Parse a timestamp line that looks like this: spaces, "when:", spaces, timestamp, newline
|
||||
// The string is NOT null terminated; however we do know it contains a newline, so stop when we reach it
|
||||
static bool parse_timestamp(const char *str, time_t *out_when) {
|
||||
static bool parse_timestamp(const char *str, time_t *out_when)
|
||||
{
|
||||
const char *cursor = str;
|
||||
/* Advance past spaces */
|
||||
while (*cursor == ' ')
|
||||
|
@ -220,7 +240,8 @@ static bool parse_timestamp(const char *str, time_t *out_when) {
|
|||
|
||||
/* Try to parse a timestamp. */
|
||||
long timestamp = 0;
|
||||
if (isdigit(*cursor) && (timestamp = strtol(cursor, NULL, 0)) > 0) {
|
||||
if (isdigit(*cursor) && (timestamp = strtol(cursor, NULL, 0)) > 0)
|
||||
{
|
||||
*out_when = (time_t)timestamp;
|
||||
return true;
|
||||
}
|
||||
|
@ -230,7 +251,8 @@ static bool parse_timestamp(const char *str, time_t *out_when) {
|
|||
// Returns a pointer to the start of the next line, or NULL
|
||||
// The next line must itself end with a newline
|
||||
// Note that the string is not null terminated
|
||||
static const char *next_line(const char *start, size_t length) {
|
||||
static const char *next_line(const char *start, size_t length)
|
||||
{
|
||||
/* Handle the hopeless case */
|
||||
if (length < 1)
|
||||
return NULL;
|
||||
|
@ -240,17 +262,20 @@ static const char *next_line(const char *start, size_t length) {
|
|||
|
||||
/* Skip past the next newline */
|
||||
const char *nextline = (const char *)memchr(start, '\n', length);
|
||||
if (! nextline || nextline >= end) {
|
||||
if (! nextline || nextline >= end)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
/* Skip past the newline character itself */
|
||||
if (++nextline >= end) {
|
||||
if (++nextline >= end)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Make sure this new line is itself "newline terminated". If it's not, return NULL; */
|
||||
const char *next_newline = (const char *)memchr(nextline, '\n', end - nextline);
|
||||
if (! next_newline) {
|
||||
if (! next_newline)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -267,7 +292,8 @@ static size_t offset_of_next_item_fish_2_0(const char *begin, size_t mmap_length
|
|||
{
|
||||
size_t cursor = *inout_cursor;
|
||||
size_t result = (size_t)(-1);
|
||||
while (cursor < mmap_length) {
|
||||
while (cursor < mmap_length)
|
||||
{
|
||||
const char * const line_start = begin + cursor;
|
||||
|
||||
/* Advance the cursor to the next line */
|
||||
|
@ -294,7 +320,8 @@ static size_t offset_of_next_item_fish_2_0(const char *begin, size_t mmap_length
|
|||
continue;
|
||||
|
||||
/* At this point, we know line_start is at the beginning of an item. But maybe we want to skip this item because of timestamps. A 0 cutoff means we don't care; if we do care, then try parsing out a timestamp. */
|
||||
if (cutoff_timestamp != 0) {
|
||||
if (cutoff_timestamp != 0)
|
||||
{
|
||||
/* Hackish fast way to skip items created after our timestamp. This is the mechanism by which we avoid "seeing" commands from other sessions that started after we started. We try hard to ensure that our items are sorted by their timestamps, so in theory we could just break, but I don't think that works well if (for example) the clock changes. So we'll read all subsequent items.
|
||||
*/
|
||||
const char * const end = begin + mmap_length;
|
||||
|
@ -305,7 +332,8 @@ static size_t offset_of_next_item_fish_2_0(const char *begin, size_t mmap_length
|
|||
const char *interior_line;
|
||||
for (interior_line = next_line(line_start, end - line_start);
|
||||
interior_line != NULL && ! has_timestamp;
|
||||
interior_line = next_line(interior_line, end - interior_line)) {
|
||||
interior_line = next_line(interior_line, end - interior_line))
|
||||
{
|
||||
|
||||
/* If the first character is not a space, it's not an interior line, so we're done */
|
||||
if (interior_line[0] != ' ')
|
||||
|
@ -319,7 +347,8 @@ static size_t offset_of_next_item_fish_2_0(const char *begin, size_t mmap_length
|
|||
}
|
||||
|
||||
/* Skip this item if the timestamp is at or after our cutoff. */
|
||||
if (has_timestamp && timestamp >= cutoff_timestamp) {
|
||||
if (has_timestamp && timestamp >= cutoff_timestamp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -335,7 +364,8 @@ static size_t offset_of_next_item_fish_2_0(const char *begin, size_t mmap_length
|
|||
|
||||
// Same as offset_of_next_item_fish_2_0, but for fish 1.x (pre fishfish)
|
||||
// Adapted from history_populate_from_mmap in history.c
|
||||
static size_t offset_of_next_item_fish_1_x(const char *begin, size_t mmap_length, size_t *inout_cursor, time_t cutoff_timestamp) {
|
||||
static size_t offset_of_next_item_fish_1_x(const char *begin, size_t mmap_length, size_t *inout_cursor, time_t cutoff_timestamp)
|
||||
{
|
||||
if (mmap_length == 0 || *inout_cursor >= mmap_length)
|
||||
return (size_t)(-1);
|
||||
|
||||
|
@ -384,9 +414,11 @@ static size_t offset_of_next_item_fish_1_x(const char *begin, size_t mmap_length
|
|||
}
|
||||
|
||||
// Returns the offset of the next item based on the given history type, or -1
|
||||
static size_t offset_of_next_item(const char *begin, size_t mmap_length, history_file_type_t mmap_type, size_t *inout_cursor, time_t cutoff_timestamp) {
|
||||
static size_t offset_of_next_item(const char *begin, size_t mmap_length, history_file_type_t mmap_type, size_t *inout_cursor, time_t cutoff_timestamp)
|
||||
{
|
||||
size_t result;
|
||||
switch (mmap_type) {
|
||||
switch (mmap_type)
|
||||
{
|
||||
case history_type_fish_2_0:
|
||||
result = offset_of_next_item_fish_2_0(begin, mmap_length, inout_cursor, cutoff_timestamp);
|
||||
break;
|
||||
|
@ -404,7 +436,8 @@ static size_t offset_of_next_item(const char *begin, size_t mmap_length, history
|
|||
return result;
|
||||
}
|
||||
|
||||
history_t & history_t::history_with_name(const wcstring &name) {
|
||||
history_t & history_t::history_with_name(const wcstring &name)
|
||||
{
|
||||
/* Note that histories are currently never deleted, so we can return a reference to them without using something like shared_ptr */
|
||||
scoped_lock locker(hist_lock);
|
||||
history_t *& current = histories[name];
|
||||
|
@ -435,7 +468,8 @@ void history_t::add(const history_item_t &item)
|
|||
scoped_lock locker(lock);
|
||||
|
||||
/* Try merging with the last item */
|
||||
if (! new_items.empty() && new_items.back().merge(item)) {
|
||||
if (! new_items.empty() && new_items.back().merge(item))
|
||||
{
|
||||
/* We merged, so we don't have to add anything */
|
||||
}
|
||||
else
|
||||
|
@ -451,7 +485,8 @@ void history_t::add(const history_item_t &item)
|
|||
save_timestamp = now;
|
||||
|
||||
/* This might be a good candidate for moving to a background thread */
|
||||
if((now > save_timestamp + SAVE_INTERVAL) || (unsaved_item_count >= SAVE_COUNT)) {
|
||||
if ((now > save_timestamp + SAVE_INTERVAL) || (unsaved_item_count >= SAVE_COUNT))
|
||||
{
|
||||
time_profiler_t profiler("save_internal");
|
||||
this->save_internal();
|
||||
}
|
||||
|
@ -471,9 +506,12 @@ void history_t::remove(const wcstring &str)
|
|||
/* Remove from our list of new items */
|
||||
for (std::vector<history_item_t>::iterator iter = new_items.begin(); iter != new_items.end();)
|
||||
{
|
||||
if (iter->str() == str) {
|
||||
if (iter->str() == str)
|
||||
{
|
||||
iter = new_items.erase(iter);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
|
@ -486,7 +524,8 @@ void history_t::get_string_representation(wcstring &result, const wcstring &sepa
|
|||
bool first = true;
|
||||
|
||||
/* Append new items */
|
||||
for (std::vector<history_item_t>::const_reverse_iterator iter=new_items.rbegin(); iter < new_items.rend(); ++iter) {
|
||||
for (std::vector<history_item_t>::const_reverse_iterator iter=new_items.rbegin(); iter < new_items.rend(); ++iter)
|
||||
{
|
||||
if (! first)
|
||||
result.append(separator);
|
||||
result.append(iter->str());
|
||||
|
@ -495,7 +534,8 @@ void history_t::get_string_representation(wcstring &result, const wcstring &sepa
|
|||
|
||||
/* Append old items */
|
||||
load_old_if_needed();
|
||||
for (std::vector<size_t>::const_reverse_iterator iter = old_item_offsets.rbegin(); iter != old_item_offsets.rend(); ++iter) {
|
||||
for (std::vector<size_t>::const_reverse_iterator iter = old_item_offsets.rbegin(); iter != old_item_offsets.rend(); ++iter)
|
||||
{
|
||||
size_t offset = *iter;
|
||||
const history_item_t item = history_t::decode_item(mmap_start + offset, mmap_length - offset, mmap_type);
|
||||
if (! first)
|
||||
|
@ -505,7 +545,8 @@ void history_t::get_string_representation(wcstring &result, const wcstring &sepa
|
|||
}
|
||||
}
|
||||
|
||||
history_item_t history_t::item_at_index(size_t idx) {
|
||||
history_item_t history_t::item_at_index(size_t idx)
|
||||
{
|
||||
scoped_lock locker(lock);
|
||||
|
||||
/* 0 is considered an invalid index */
|
||||
|
@ -514,7 +555,8 @@ history_item_t history_t::item_at_index(size_t idx) {
|
|||
|
||||
/* idx=0 corresponds to last item in new_items */
|
||||
size_t new_item_count = new_items.size();
|
||||
if (idx < new_item_count) {
|
||||
if (idx < new_item_count)
|
||||
{
|
||||
return new_items.at(new_item_count - idx - 1);
|
||||
}
|
||||
|
||||
|
@ -522,7 +564,8 @@ history_item_t history_t::item_at_index(size_t idx) {
|
|||
idx -= new_item_count;
|
||||
load_old_if_needed();
|
||||
size_t old_item_count = old_item_offsets.size();
|
||||
if (idx < old_item_count) {
|
||||
if (idx < old_item_count)
|
||||
{
|
||||
/* idx=0 corresponds to last item in old_item_offsets */
|
||||
size_t offset = old_item_offsets.at(old_item_count - idx - 1);
|
||||
return history_t::decode_item(mmap_start + offset, mmap_length - offset, mmap_type);
|
||||
|
@ -533,18 +576,22 @@ history_item_t history_t::item_at_index(size_t idx) {
|
|||
}
|
||||
|
||||
/* Read one line, stripping off any newline, and updating cursor. Note that our input string is NOT null terminated; it's just a memory mapped file. */
|
||||
static size_t read_line(const char *base, size_t cursor, size_t len, std::string &result) {
|
||||
static size_t read_line(const char *base, size_t cursor, size_t len, std::string &result)
|
||||
{
|
||||
/* Locate the newline */
|
||||
assert(cursor <= len);
|
||||
const char *start = base + cursor;
|
||||
const char *newline = (char *)memchr(start, '\n', len - cursor);
|
||||
if (newline != NULL) {
|
||||
if (newline != NULL)
|
||||
{
|
||||
/* We found a newline. */
|
||||
result.assign(start, newline - start);
|
||||
|
||||
/* Return the amount to advance the cursor; skip over the newline */
|
||||
return newline - start + 1;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We ran off the end */
|
||||
result.clear();
|
||||
return len - cursor;
|
||||
|
@ -552,7 +599,8 @@ static size_t read_line(const char *base, size_t cursor, size_t len, std::string
|
|||
}
|
||||
|
||||
/* Trims leading spaces in the given string, returning how many there were */
|
||||
static size_t trim_leading_spaces(std::string &str) {
|
||||
static size_t trim_leading_spaces(std::string &str)
|
||||
{
|
||||
size_t i = 0, max = str.size();
|
||||
while (i < max && str[i] == ' ')
|
||||
i++;
|
||||
|
@ -560,9 +608,11 @@ static size_t trim_leading_spaces(std::string &str) {
|
|||
return i;
|
||||
}
|
||||
|
||||
static bool extract_prefix(std::string &key, std::string &value, const std::string &line) {
|
||||
static bool extract_prefix(std::string &key, std::string &value, const std::string &line)
|
||||
{
|
||||
size_t where = line.find(":");
|
||||
if (where != std::string::npos) {
|
||||
if (where != std::string::npos)
|
||||
{
|
||||
key = line.substr(0, where);
|
||||
|
||||
// skip a space after the : if necessary
|
||||
|
@ -578,7 +628,8 @@ static bool extract_prefix(std::string &key, std::string &value, const std::stri
|
|||
}
|
||||
|
||||
/* Decode an item via the fish 2.0 format */
|
||||
history_item_t history_t::decode_item_fish_2_0(const char *base, size_t len) {
|
||||
history_item_t history_t::decode_item_fish_2_0(const char *base, size_t len)
|
||||
{
|
||||
wcstring cmd;
|
||||
time_t when = 0;
|
||||
path_list_t paths;
|
||||
|
@ -596,7 +647,8 @@ history_item_t history_t::decode_item_fish_2_0(const char *base, size_t len) {
|
|||
cmd = str2wcstring(value);
|
||||
|
||||
/* Read the remaining lines */
|
||||
for (;;) {
|
||||
for (;;)
|
||||
{
|
||||
/* Read a line */
|
||||
size_t advance = read_line(base, cursor, len, line);
|
||||
|
||||
|
@ -615,15 +667,20 @@ history_item_t history_t::decode_item_fish_2_0(const char *base, size_t len) {
|
|||
unescape_yaml(value);
|
||||
cursor += advance;
|
||||
|
||||
if (key == "when") {
|
||||
if (key == "when")
|
||||
{
|
||||
/* Parse an int from the timestamp */
|
||||
long tmp = 0;
|
||||
if (sscanf(value.c_str(), "%ld", &tmp) > 0) {
|
||||
if (sscanf(value.c_str(), "%ld", &tmp) > 0)
|
||||
{
|
||||
when = tmp;
|
||||
}
|
||||
} else if (key == "paths") {
|
||||
}
|
||||
else if (key == "paths")
|
||||
{
|
||||
/* Read lines starting with " - " until we can't read any more */
|
||||
for (;;) {
|
||||
for (;;)
|
||||
{
|
||||
size_t advance = read_line(base, cursor, len, line);
|
||||
if (trim_leading_spaces(line) <= indent)
|
||||
break;
|
||||
|
@ -645,11 +702,16 @@ done:
|
|||
return history_item_t(cmd, when, paths);
|
||||
}
|
||||
|
||||
history_item_t history_t::decode_item(const char *base, size_t len, history_file_type_t type) {
|
||||
switch (type) {
|
||||
case history_type_fish_1_x: return history_t::decode_item_fish_1_x(base, len);
|
||||
case history_type_fish_2_0: return history_t::decode_item_fish_2_0(base, len);
|
||||
default: return history_item_t(L"");
|
||||
history_item_t history_t::decode_item(const char *base, size_t len, history_file_type_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case history_type_fish_1_x:
|
||||
return history_t::decode_item_fish_1_x(base, len);
|
||||
case history_type_fish_2_0:
|
||||
return history_t::decode_item_fish_2_0(base, len);
|
||||
default:
|
||||
return history_item_t(L"");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -679,7 +741,8 @@ static wcstring history_unescape_newlines_fish_1_x( const wcstring &in_str )
|
|||
|
||||
|
||||
/* Decode an item via the fish 1.x format. Adapted from fish 1.x's item_get(). */
|
||||
history_item_t history_t::decode_item_fish_1_x(const char *begin, size_t length) {
|
||||
history_item_t history_t::decode_item_fish_1_x(const char *begin, size_t length)
|
||||
{
|
||||
|
||||
const char *end = begin + length;
|
||||
const char *pos=begin;
|
||||
|
@ -768,13 +831,18 @@ history_item_t history_t::decode_item_fish_1_x(const char *begin, size_t length)
|
|||
|
||||
|
||||
/* Try to infer the history file type based on inspecting the data */
|
||||
static history_file_type_t infer_file_type(const char *data, size_t len) {
|
||||
static history_file_type_t infer_file_type(const char *data, size_t len)
|
||||
{
|
||||
history_file_type_t result = history_type_unknown;
|
||||
if (len > 0) {
|
||||
if (len > 0)
|
||||
{
|
||||
/* Old fish started with a # */
|
||||
if (data[0] == '#') {
|
||||
if (data[0] == '#')
|
||||
{
|
||||
result = history_type_fish_1_x;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Assume new fish */
|
||||
result = history_type_fish_2_0;
|
||||
}
|
||||
|
@ -786,7 +854,8 @@ void history_t::populate_from_mmap(void)
|
|||
{
|
||||
mmap_type = infer_file_type(mmap_start, mmap_length);
|
||||
size_t cursor = 0;
|
||||
for (;;) {
|
||||
for (;;)
|
||||
{
|
||||
size_t offset = offset_of_next_item(mmap_start, mmap_length, mmap_type, &cursor, birth_timestamp);
|
||||
// If we get back -1, we're done
|
||||
if (offset == (size_t)(-1))
|
||||
|
@ -838,7 +907,8 @@ bool history_t::load_old_if_needed(void)
|
|||
//signal_block();
|
||||
|
||||
bool ok = false;
|
||||
if (map_file(name, &mmap_start, &mmap_length)) {
|
||||
if (map_file(name, &mmap_start, &mmap_length))
|
||||
{
|
||||
// Here we've mapped the file
|
||||
ok = true;
|
||||
time_profiler_t profiler("populate_from_mmap");
|
||||
|
@ -849,25 +919,30 @@ bool history_t::load_old_if_needed(void)
|
|||
return ok;
|
||||
}
|
||||
|
||||
void history_search_t::skip_matches(const wcstring_list_t &skips) {
|
||||
void history_search_t::skip_matches(const wcstring_list_t &skips)
|
||||
{
|
||||
external_skips = skips;
|
||||
std::sort(external_skips.begin(), external_skips.end());
|
||||
}
|
||||
|
||||
bool history_search_t::should_skip_match(const wcstring &str) const {
|
||||
bool history_search_t::should_skip_match(const wcstring &str) const
|
||||
{
|
||||
return std::binary_search(external_skips.begin(), external_skips.end(), str);
|
||||
}
|
||||
|
||||
bool history_search_t::go_forwards() {
|
||||
bool history_search_t::go_forwards()
|
||||
{
|
||||
/* Pop the top index (if more than one) and return if we have any left */
|
||||
if (prev_matches.size() > 1) {
|
||||
if (prev_matches.size() > 1)
|
||||
{
|
||||
prev_matches.pop_back();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool history_search_t::go_backwards() {
|
||||
bool history_search_t::go_backwards()
|
||||
{
|
||||
/* Backwards means increasing our index */
|
||||
const size_t max_idx = (size_t)(-1);
|
||||
|
||||
|
@ -878,16 +953,19 @@ bool history_search_t::go_backwards() {
|
|||
if (idx == max_idx)
|
||||
return false;
|
||||
|
||||
while (++idx < max_idx) {
|
||||
while (++idx < max_idx)
|
||||
{
|
||||
const history_item_t item = history->item_at_index(idx);
|
||||
/* We're done if it's empty */
|
||||
if (item.empty()) {
|
||||
if (item.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Look for a term that matches and that we haven't seen before */
|
||||
const wcstring &str = item.str();
|
||||
if (item.matches_search(term, search_type) && ! match_already_made(str) && ! should_skip_match(str)) {
|
||||
if (item.matches_search(term, search_type) && ! match_already_made(str) && ! should_skip_match(str))
|
||||
{
|
||||
prev_matches.push_back(prev_match_t(idx, item));
|
||||
return true;
|
||||
}
|
||||
|
@ -896,36 +974,43 @@ bool history_search_t::go_backwards() {
|
|||
}
|
||||
|
||||
/** Goes to the end (forwards) */
|
||||
void history_search_t::go_to_end(void) {
|
||||
void history_search_t::go_to_end(void)
|
||||
{
|
||||
prev_matches.clear();
|
||||
}
|
||||
|
||||
/** Returns if we are at the end, which is where we start. */
|
||||
bool history_search_t::is_at_end(void) const {
|
||||
bool history_search_t::is_at_end(void) const
|
||||
{
|
||||
return prev_matches.empty();
|
||||
}
|
||||
|
||||
|
||||
/** Goes to the beginning (backwards) */
|
||||
void history_search_t::go_to_beginning(void) {
|
||||
void history_search_t::go_to_beginning(void)
|
||||
{
|
||||
/* Just go backwards as far as we can */
|
||||
while (go_backwards())
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
history_item_t history_search_t::current_item() const {
|
||||
history_item_t history_search_t::current_item() const
|
||||
{
|
||||
assert(! prev_matches.empty());
|
||||
return prev_matches.back().second;
|
||||
}
|
||||
|
||||
wcstring history_search_t::current_string() const {
|
||||
wcstring history_search_t::current_string() const
|
||||
{
|
||||
history_item_t item = this->current_item();
|
||||
return item.str();
|
||||
}
|
||||
|
||||
bool history_search_t::match_already_made(const wcstring &match) const {
|
||||
for (std::vector<prev_match_t>::const_iterator iter = prev_matches.begin(); iter != prev_matches.end(); ++iter) {
|
||||
bool history_search_t::match_already_made(const wcstring &match) const
|
||||
{
|
||||
for (std::vector<prev_match_t>::const_iterator iter = prev_matches.begin(); iter != prev_matches.end(); ++iter)
|
||||
{
|
||||
if (iter->second.str() == match)
|
||||
return true;
|
||||
}
|
||||
|
@ -943,27 +1028,36 @@ static void replace_all(std::string &str, const char *needle, const char *replac
|
|||
}
|
||||
}
|
||||
|
||||
static void escape_yaml(std::string &str) {
|
||||
static void escape_yaml(std::string &str)
|
||||
{
|
||||
replace_all(str, "\\", "\\\\"); //replace one backslash with two
|
||||
replace_all(str, "\n", "\\n"); //replace newline with backslash + literal n
|
||||
}
|
||||
|
||||
static void unescape_yaml(std::string &str) {
|
||||
static void unescape_yaml(std::string &str)
|
||||
{
|
||||
bool prev_escape = false;
|
||||
for (size_t idx = 0; idx < str.size(); idx++) {
|
||||
for (size_t idx = 0; idx < str.size(); idx++)
|
||||
{
|
||||
char c = str.at(idx);
|
||||
if (prev_escape) {
|
||||
if (c == '\\') {
|
||||
if (prev_escape)
|
||||
{
|
||||
if (c == '\\')
|
||||
{
|
||||
/* Two backslashes in a row. Delete this one */
|
||||
str.erase(idx, 1);
|
||||
idx--;
|
||||
} else if (c == 'n') {
|
||||
}
|
||||
else if (c == 'n')
|
||||
{
|
||||
/* Replace backslash + n with an actual newline */
|
||||
str.replace(idx - 1, 2, "\n");
|
||||
idx--;
|
||||
}
|
||||
prev_escape = false;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
prev_escape = (c == '\\');
|
||||
}
|
||||
}
|
||||
|
@ -986,7 +1080,8 @@ static wcstring history_filename(const wcstring &name, const wcstring &suffix)
|
|||
void history_t::clear_file_state()
|
||||
{
|
||||
/* Erase everything we know about our file */
|
||||
if (mmap_start != NULL && mmap_start != MAP_FAILED) {
|
||||
if (mmap_start != NULL && mmap_start != MAP_FAILED)
|
||||
{
|
||||
munmap((void *)mmap_start, mmap_length);
|
||||
}
|
||||
mmap_start = NULL;
|
||||
|
@ -996,13 +1091,16 @@ void history_t::clear_file_state()
|
|||
save_timestamp=time(0);
|
||||
}
|
||||
|
||||
void history_t::compact_new_items() {
|
||||
void history_t::compact_new_items()
|
||||
{
|
||||
/* Keep only the most recent items with the given contents. This algorithm could be made more efficient, but likely would consume more memory too. */
|
||||
std::set<wcstring> seen;
|
||||
size_t idx = new_items.size();
|
||||
while (idx--) {
|
||||
while (idx--)
|
||||
{
|
||||
const history_item_t &item = new_items[idx];
|
||||
if (! seen.insert(item.contents).second) {
|
||||
if (! seen.insert(item.contents).second)
|
||||
{
|
||||
// This item was not inserted because it was already in the set, so delete the item at this index
|
||||
new_items.erase(new_items.begin() + idx);
|
||||
}
|
||||
|
@ -1036,10 +1134,12 @@ void history_t::save_internal()
|
|||
/* Map in existing items (which may have changed out from underneath us, so don't trust our old mmap'd data) */
|
||||
const char *local_mmap_start = NULL;
|
||||
size_t local_mmap_size = 0;
|
||||
if (map_file(name, &local_mmap_start, &local_mmap_size)) {
|
||||
if (map_file(name, &local_mmap_start, &local_mmap_size))
|
||||
{
|
||||
const history_file_type_t local_mmap_type = infer_file_type(local_mmap_start, local_mmap_size);
|
||||
size_t cursor = 0;
|
||||
for (;;) {
|
||||
for (;;)
|
||||
{
|
||||
size_t offset = offset_of_next_item(local_mmap_start, local_mmap_size, local_mmap_type, &cursor, 0);
|
||||
/* If we get back -1, we're done */
|
||||
if (offset == (size_t)(-1))
|
||||
|
@ -1053,11 +1153,15 @@ void history_t::save_internal()
|
|||
continue;
|
||||
}
|
||||
/* The old item may actually be more recent than our new item, if it came from another session. Insert all new items at the given index with an earlier timestamp. */
|
||||
for (; new_item_iter != new_items.end(); ++new_item_iter) {
|
||||
if (new_item_iter->timestamp() < old_item.timestamp()) {
|
||||
for (; new_item_iter != new_items.end(); ++new_item_iter)
|
||||
{
|
||||
if (new_item_iter->timestamp() < old_item.timestamp())
|
||||
{
|
||||
/* This "new item" is in fact older. */
|
||||
lru.add_item(*new_item_iter);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The new item is not older. */
|
||||
break;
|
||||
}
|
||||
|
@ -1081,9 +1185,11 @@ void history_t::save_internal()
|
|||
if ((out=wfopen(tmp_name, "w")))
|
||||
{
|
||||
/* Write them out */
|
||||
for (history_lru_cache_t::iterator iter = lru.begin(); iter != lru.end(); ++iter) {
|
||||
for (history_lru_cache_t::iterator iter = lru.begin(); iter != lru.end(); ++iter)
|
||||
{
|
||||
const history_lru_node_t *node = *iter;
|
||||
if (! node->write_yaml_to_file(out)) {
|
||||
if (! node->write_yaml_to_file(out))
|
||||
{
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
@ -1159,14 +1265,17 @@ static bool should_import_bash_history_line(const std::string &line)
|
|||
return false;
|
||||
|
||||
/* Very naive tests! Skip export; probably should skip others. */
|
||||
const char * const ignore_prefixes[] = {
|
||||
const char * const ignore_prefixes[] =
|
||||
{
|
||||
"export ",
|
||||
"#"
|
||||
};
|
||||
|
||||
for (size_t i=0; i < sizeof ignore_prefixes / sizeof *ignore_prefixes; i++) {
|
||||
for (size_t i=0; i < sizeof ignore_prefixes / sizeof *ignore_prefixes; i++)
|
||||
{
|
||||
const char *prefix = ignore_prefixes[i];
|
||||
if (! line.compare(0, strlen(prefix), prefix)) {
|
||||
if (! line.compare(0, strlen(prefix), prefix))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1184,15 +1293,18 @@ void history_t::populate_from_bash(FILE *stream)
|
|||
Ignore a few commands that are bash-specific. This list ought to be expanded.
|
||||
*/
|
||||
std::string line;
|
||||
for (;;) {
|
||||
for (;;)
|
||||
{
|
||||
line.clear();
|
||||
bool success = false, has_newline = false;
|
||||
|
||||
/* Loop until we've read a line */
|
||||
do {
|
||||
do
|
||||
{
|
||||
char buff[128];
|
||||
success = !! fgets(buff, sizeof buff, stream);
|
||||
if (success) {
|
||||
if (success)
|
||||
{
|
||||
/* Skip the newline */
|
||||
char *newline = strchr(buff, '\n');
|
||||
if (newline) *newline = '\0';
|
||||
|
@ -1201,10 +1313,12 @@ void history_t::populate_from_bash(FILE *stream)
|
|||
/* Append what we've got */
|
||||
line.append(buff);
|
||||
}
|
||||
} while (success && ! has_newline);
|
||||
}
|
||||
while (success && ! has_newline);
|
||||
|
||||
/* Maybe add this line */
|
||||
if (should_import_bash_history_line(line)) {
|
||||
if (should_import_bash_history_line(line))
|
||||
{
|
||||
this->add(str2wcstring(line));
|
||||
}
|
||||
|
||||
|
@ -1221,7 +1335,8 @@ void history_init()
|
|||
void history_destroy()
|
||||
{
|
||||
/* Save all histories */
|
||||
for (std::map<wcstring, history_t *>::iterator iter = histories.begin(); iter != histories.end(); ++iter) {
|
||||
for (std::map<wcstring, history_t *>::iterator iter = histories.begin(); iter != histories.end(); ++iter)
|
||||
{
|
||||
iter->second->save();
|
||||
}
|
||||
}
|
||||
|
@ -1234,15 +1349,20 @@ void history_sanity_check()
|
|||
*/
|
||||
}
|
||||
|
||||
int file_detection_context_t::perform_file_detection(bool test_all) {
|
||||
int file_detection_context_t::perform_file_detection(bool test_all)
|
||||
{
|
||||
ASSERT_IS_BACKGROUND_THREAD();
|
||||
valid_paths.clear();
|
||||
int result = 1;
|
||||
for (path_list_t::const_iterator iter = potential_paths.begin(); iter != potential_paths.end(); ++iter) {
|
||||
if (path_is_valid(*iter, working_directory)) {
|
||||
for (path_list_t::const_iterator iter = potential_paths.begin(); iter != potential_paths.end(); ++iter)
|
||||
{
|
||||
if (path_is_valid(*iter, working_directory))
|
||||
{
|
||||
/* Push the original (possibly relative) path */
|
||||
valid_paths.push_back(*iter);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Not a valid path */
|
||||
result = 0;
|
||||
if (! test_all)
|
||||
|
@ -1252,7 +1372,8 @@ int file_detection_context_t::perform_file_detection(bool test_all) {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool file_detection_context_t::paths_are_valid(const path_list_t &paths) {
|
||||
bool file_detection_context_t::paths_are_valid(const path_list_t &paths)
|
||||
{
|
||||
this->potential_paths = paths;
|
||||
return perform_file_detection(false) > 0;
|
||||
}
|
||||
|
@ -1265,13 +1386,15 @@ file_detection_context_t::file_detection_context_t(history_t *hist, const wcstri
|
|||
{
|
||||
}
|
||||
|
||||
static int threaded_perform_file_detection(file_detection_context_t *ctx) {
|
||||
static int threaded_perform_file_detection(file_detection_context_t *ctx)
|
||||
{
|
||||
ASSERT_IS_BACKGROUND_THREAD();
|
||||
assert(ctx != NULL);
|
||||
return ctx->perform_file_detection(true /* test all */);
|
||||
}
|
||||
|
||||
static void perform_file_detection_done(file_detection_context_t *ctx, int success) {
|
||||
static void perform_file_detection_done(file_detection_context_t *ctx, int success)
|
||||
{
|
||||
/* Now that file detection is done, create the history item */
|
||||
ctx->history->add(ctx->command, ctx->valid_paths);
|
||||
|
||||
|
@ -1279,7 +1402,8 @@ static void perform_file_detection_done(file_detection_context_t *ctx, int succe
|
|||
delete ctx;
|
||||
}
|
||||
|
||||
static bool string_could_be_path(const wcstring &potential_path) {
|
||||
static bool string_could_be_path(const wcstring &potential_path)
|
||||
{
|
||||
// Assume that things with leading dashes aren't paths
|
||||
if (potential_path.empty() || potential_path.at(0) == L'-')
|
||||
return false;
|
||||
|
@ -1297,11 +1421,14 @@ void history_t::add_with_file_detection(const wcstring &str)
|
|||
tok_next(&tokenizer))
|
||||
{
|
||||
int type = tok_last_type(&tokenizer);
|
||||
if (type == TOK_STRING) {
|
||||
if (type == TOK_STRING)
|
||||
{
|
||||
const wchar_t *token_cstr = tok_last(&tokenizer);
|
||||
if (token_cstr) {
|
||||
if (token_cstr)
|
||||
{
|
||||
wcstring potential_path = token_cstr;
|
||||
if (unescape_string(potential_path, false) && string_could_be_path(potential_path)) {
|
||||
if (unescape_string(potential_path, false) && string_could_be_path(potential_path))
|
||||
{
|
||||
potential_paths.push_back(potential_path);
|
||||
}
|
||||
}
|
||||
|
@ -1309,7 +1436,8 @@ void history_t::add_with_file_detection(const wcstring &str)
|
|||
}
|
||||
tok_destroy(&tokenizer);
|
||||
|
||||
if (! potential_paths.empty()) {
|
||||
if (! potential_paths.empty())
|
||||
{
|
||||
/* We have some paths. Make a context. */
|
||||
file_detection_context_t *context = new file_detection_context_t(this, str);
|
||||
|
||||
|
|
46
history.h
46
history.h
|
@ -15,7 +15,8 @@
|
|||
|
||||
typedef std::vector<wcstring> path_list_t;
|
||||
|
||||
enum history_search_type_t {
|
||||
enum history_search_type_t
|
||||
{
|
||||
/** The history searches for strings containing the given string */
|
||||
HISTORY_SEARCH_TYPE_CONTAINS,
|
||||
|
||||
|
@ -23,7 +24,8 @@ enum history_search_type_t {
|
|||
HISTORY_SEARCH_TYPE_PREFIX
|
||||
};
|
||||
|
||||
class history_item_t {
|
||||
class history_item_t
|
||||
{
|
||||
friend class history_t;
|
||||
friend class history_lru_node_t;
|
||||
friend class history_tests_t;
|
||||
|
@ -45,17 +47,30 @@ class history_item_t {
|
|||
path_list_t required_paths;
|
||||
|
||||
public:
|
||||
const wcstring &str() const { return contents; }
|
||||
bool empty() const { return contents.empty(); }
|
||||
const wcstring &str() const
|
||||
{
|
||||
return contents;
|
||||
}
|
||||
bool empty() const
|
||||
{
|
||||
return contents.empty();
|
||||
}
|
||||
|
||||
/* Whether our contents matches a search term. */
|
||||
bool matches_search(const wcstring &term, enum history_search_type_t type) const;
|
||||
|
||||
time_t timestamp() const { return creation_timestamp; }
|
||||
time_t timestamp() const
|
||||
{
|
||||
return creation_timestamp;
|
||||
}
|
||||
|
||||
const path_list_t &get_required_paths() const { return required_paths; }
|
||||
const path_list_t &get_required_paths() const
|
||||
{
|
||||
return required_paths;
|
||||
}
|
||||
|
||||
bool operator==(const history_item_t &other) const {
|
||||
bool operator==(const history_item_t &other) const
|
||||
{
|
||||
return contents == other.contents &&
|
||||
creation_timestamp == other.creation_timestamp &&
|
||||
required_paths == other.required_paths;
|
||||
|
@ -63,13 +78,15 @@ class history_item_t {
|
|||
};
|
||||
|
||||
/* The type of file that we mmap'd */
|
||||
enum history_file_type_t {
|
||||
enum history_file_type_t
|
||||
{
|
||||
history_type_unknown,
|
||||
history_type_fish_2_0,
|
||||
history_type_fish_1_x
|
||||
};
|
||||
|
||||
class history_t {
|
||||
class history_t
|
||||
{
|
||||
friend class history_tests_t;
|
||||
private:
|
||||
/** No copying */
|
||||
|
@ -174,7 +191,8 @@ public:
|
|||
bool is_deleted(const history_item_t &item) const;
|
||||
};
|
||||
|
||||
class history_search_t {
|
||||
class history_search_t
|
||||
{
|
||||
|
||||
/** The history in which we are searching */
|
||||
history_t * history;
|
||||
|
@ -200,7 +218,10 @@ class history_search_t {
|
|||
public:
|
||||
|
||||
/** Gets the search term */
|
||||
const wcstring &get_term() const { return term; }
|
||||
const wcstring &get_term() const
|
||||
{
|
||||
return term;
|
||||
}
|
||||
|
||||
/** Sets additional string matches to skip */
|
||||
void skip_matches(const wcstring_list_t &skips);
|
||||
|
@ -262,7 +283,8 @@ void history_destroy();
|
|||
void history_sanity_check();
|
||||
|
||||
/* A helper class for threaded detection of paths */
|
||||
struct file_detection_context_t {
|
||||
struct file_detection_context_t
|
||||
{
|
||||
|
||||
/* Constructor */
|
||||
file_detection_context_t(history_t *hist, const wcstring &cmd);
|
||||
|
|
28
input.cpp
28
input.cpp
|
@ -306,20 +306,30 @@ void update_fish_term256(void)
|
|||
/* Infer term256 support. If fish_term256 is set, we respect it; otherwise try to detect it from the TERM variable */
|
||||
env_var_t fish_term256 = env_get_string(L"fish_term256");
|
||||
bool support_term256;
|
||||
if (! fish_term256.missing_or_empty()) {
|
||||
if (! fish_term256.missing_or_empty())
|
||||
{
|
||||
support_term256 = from_string<bool>(fish_term256);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
env_var_t term = env_get_string(L"TERM");
|
||||
if (term.missing()) {
|
||||
if (term.missing())
|
||||
{
|
||||
support_term256 = false;
|
||||
} else if (term.find(L"256color") != wcstring::npos) {
|
||||
}
|
||||
else if (term.find(L"256color") != wcstring::npos)
|
||||
{
|
||||
/* Explicitly supported */
|
||||
support_term256 = true;
|
||||
} else if (term.find(L"xterm") != wcstring::npos) {
|
||||
}
|
||||
else if (term.find(L"xterm") != wcstring::npos)
|
||||
{
|
||||
// assume that all xterms are 256, except for OS X SnowLeopard
|
||||
env_var_t prog = env_get_string(L"TERM_PROGRAM");
|
||||
support_term256 = (prog != L"Apple_Terminal");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't know, default to false
|
||||
support_term256 = false;
|
||||
}
|
||||
|
@ -607,7 +617,8 @@ bool input_mapping_get( const wcstring &sequence, wcstring &cmd )
|
|||
*/
|
||||
static void input_terminfo_init()
|
||||
{
|
||||
const terminfo_mapping_t tinfos[] = {
|
||||
const terminfo_mapping_t tinfos[] =
|
||||
{
|
||||
TERMINFO_ADD(key_a1),
|
||||
TERMINFO_ADD(key_a3),
|
||||
TERMINFO_ADD(key_b2),
|
||||
|
@ -817,7 +828,8 @@ bool input_terminfo_get_name( const wcstring &seq, wcstring &name )
|
|||
}
|
||||
|
||||
const wcstring map_buf = format_string(L"%s", m.seq);
|
||||
if (map_buf == seq) {
|
||||
if (map_buf == seq)
|
||||
{
|
||||
name = m.name;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -94,7 +94,8 @@ static wint_t readb()
|
|||
FD_SET(env_universal_server.fd, &fdset);
|
||||
if (fd_max < env_universal_server.fd) fd_max = env_universal_server.fd;
|
||||
}
|
||||
if (ioport > 0) {
|
||||
if (ioport > 0)
|
||||
{
|
||||
FD_SET(ioport, &fdset);
|
||||
if (fd_max < ioport) fd_max = ioport;
|
||||
}
|
||||
|
|
20
intern.cpp
20
intern.cpp
|
@ -21,9 +21,11 @@
|
|||
#include "intern.h"
|
||||
|
||||
/** Comparison function for intern'd strings */
|
||||
class string_table_compare_t {
|
||||
class string_table_compare_t
|
||||
{
|
||||
public:
|
||||
bool operator()(const wchar_t *a, const wchar_t *b) const {
|
||||
bool operator()(const wchar_t *a, const wchar_t *b) const
|
||||
{
|
||||
return wcscmp(a, b) < 0;
|
||||
}
|
||||
};
|
||||
|
@ -54,17 +56,23 @@ static const wchar_t *intern_with_dup( const wchar_t *in, bool dup )
|
|||
|
||||
#if USE_SET
|
||||
string_table_t::const_iterator iter = string_table.find(in);
|
||||
if (iter != string_table.end()) {
|
||||
if (iter != string_table.end())
|
||||
{
|
||||
result = *iter;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
result = dup ? wcsdup(in) : in;
|
||||
string_table.insert(result);
|
||||
}
|
||||
#else
|
||||
string_table_t::iterator iter = std::lower_bound(string_table.begin(), string_table.end(), in, string_table_compare_t());
|
||||
if (iter != string_table.end() && wcscmp(*iter, in) == 0) {
|
||||
if (iter != string_table.end() && wcscmp(*iter, in) == 0)
|
||||
{
|
||||
result = *iter;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
result = dup ? wcsdup(in) : in;
|
||||
string_table.insert(iter, result);
|
||||
}
|
||||
|
|
9
io.cpp
9
io.cpp
|
@ -218,7 +218,8 @@ void io_print(const io_chain_t &chain)
|
|||
}
|
||||
|
||||
fprintf(stderr, "Chain %p (%ld items):\n", &chain, (long)chain.size());
|
||||
for (size_t i=0; i < chain.size(); i++) {
|
||||
for (size_t i=0; i < chain.size(); i++)
|
||||
{
|
||||
const io_data_t *io = chain.at(i);
|
||||
fprintf(stderr, "\t%lu: fd:%d, input:%s, ", (unsigned long)i, io->fd, io->is_input ? "yes" : "no");
|
||||
switch (io->io_mode)
|
||||
|
@ -259,7 +260,8 @@ const io_data_t *io_chain_t::get_io_for_fd(int fd) const
|
|||
while (idx--)
|
||||
{
|
||||
const io_data_t *data = this->at(idx);
|
||||
if (data->fd == fd) {
|
||||
if (data->fd == fd)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -272,7 +274,8 @@ io_data_t *io_chain_t::get_io_for_fd(int fd)
|
|||
while (idx--)
|
||||
{
|
||||
io_data_t *data = this->at(idx);
|
||||
if (data->fd == fd) {
|
||||
if (data->fd == fd)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
24
io.h
24
io.h
|
@ -54,35 +54,41 @@ public:
|
|||
const char *filename_cstr;
|
||||
|
||||
/** Convenience to set filename_cstr via wcstring */
|
||||
void set_filename(const wcstring &str) {
|
||||
void set_filename(const wcstring &str)
|
||||
{
|
||||
free((void *)filename_cstr);
|
||||
filename_cstr = wcs2str(str.c_str());
|
||||
}
|
||||
|
||||
/** Function to create the output buffer */
|
||||
void out_buffer_create() {
|
||||
void out_buffer_create()
|
||||
{
|
||||
out_buffer.reset(new std::vector<char>);
|
||||
}
|
||||
|
||||
/** Function to append to the buffer */
|
||||
void out_buffer_append(const char *ptr, size_t count) {
|
||||
void out_buffer_append(const char *ptr, size_t count)
|
||||
{
|
||||
assert(out_buffer.get() != NULL);
|
||||
out_buffer->insert(out_buffer->end(), ptr, ptr + count);
|
||||
}
|
||||
|
||||
/** Function to get a pointer to the buffer */
|
||||
char *out_buffer_ptr(void) {
|
||||
char *out_buffer_ptr(void)
|
||||
{
|
||||
assert(out_buffer.get() != NULL);
|
||||
return out_buffer->empty() ? NULL : &out_buffer->at(0);
|
||||
}
|
||||
|
||||
const char *out_buffer_ptr(void) const {
|
||||
const char *out_buffer_ptr(void) const
|
||||
{
|
||||
assert(out_buffer.get() != NULL);
|
||||
return out_buffer->empty() ? NULL : &out_buffer->at(0);
|
||||
}
|
||||
|
||||
/** Function to get the size of the buffer */
|
||||
size_t out_buffer_size(void) const {
|
||||
size_t out_buffer_size(void) const
|
||||
{
|
||||
assert(out_buffer.get() != NULL);
|
||||
return out_buffer->size();
|
||||
}
|
||||
|
@ -112,12 +118,14 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
~io_data_t() {
|
||||
~io_data_t()
|
||||
{
|
||||
free((void *)filename_cstr);
|
||||
}
|
||||
};
|
||||
|
||||
class io_chain_t : public std::vector<io_data_t *> {
|
||||
class io_chain_t : public std::vector<io_data_t *>
|
||||
{
|
||||
public:
|
||||
io_chain_t();
|
||||
io_chain_t(io_data_t *);
|
||||
|
|
69
iothread.cpp
69
iothread.cpp
|
@ -26,12 +26,14 @@ static int s_active_thread_count;
|
|||
|
||||
typedef unsigned char ThreadIndex_t;
|
||||
|
||||
static struct WorkerThread_t {
|
||||
static struct WorkerThread_t
|
||||
{
|
||||
ThreadIndex_t idx;
|
||||
pthread_t thread;
|
||||
} threads[IO_MAX_THREADS];
|
||||
|
||||
struct ThreadedRequest_t {
|
||||
struct ThreadedRequest_t
|
||||
{
|
||||
int sequenceNumber;
|
||||
|
||||
int (*handler)(void *);
|
||||
|
@ -40,8 +42,10 @@ struct ThreadedRequest_t {
|
|||
int handlerResult;
|
||||
};
|
||||
|
||||
static struct WorkerThread_t *next_vacant_thread_slot(void) {
|
||||
for (ThreadIndex_t i=0; i < IO_MAX_THREADS; i++) {
|
||||
static struct WorkerThread_t *next_vacant_thread_slot(void)
|
||||
{
|
||||
for (ThreadIndex_t i=0; i < IO_MAX_THREADS; i++)
|
||||
{
|
||||
if (! threads[i].thread) return &threads[i];
|
||||
}
|
||||
return NULL;
|
||||
|
@ -52,9 +56,11 @@ static std::queue<ThreadedRequest_t *> s_request_queue;
|
|||
static int s_last_sequence_number;
|
||||
static int s_read_pipe, s_write_pipe;
|
||||
|
||||
static void iothread_init(void) {
|
||||
static void iothread_init(void)
|
||||
{
|
||||
static bool inited = false;
|
||||
if (! inited) {
|
||||
if (! inited)
|
||||
{
|
||||
inited = true;
|
||||
|
||||
/* Initialize the queue lock */
|
||||
|
@ -71,21 +77,25 @@ static void iothread_init(void) {
|
|||
VOMIT_ON_FAILURE(-1 == fcntl(s_write_pipe, F_SETFD, FD_CLOEXEC));
|
||||
|
||||
/* Tell each thread its index */
|
||||
for (ThreadIndex_t i=0; i < IO_MAX_THREADS; i++) {
|
||||
for (ThreadIndex_t i=0; i < IO_MAX_THREADS; i++)
|
||||
{
|
||||
threads[i].idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_to_queue(struct ThreadedRequest_t *req) {
|
||||
static void add_to_queue(struct ThreadedRequest_t *req)
|
||||
{
|
||||
ASSERT_IS_LOCKED(s_request_queue_lock);
|
||||
s_request_queue.push(req);
|
||||
}
|
||||
|
||||
static ThreadedRequest_t *dequeue_request(void) {
|
||||
static ThreadedRequest_t *dequeue_request(void)
|
||||
{
|
||||
ThreadedRequest_t *result = NULL;
|
||||
scoped_lock lock(s_request_queue_lock);
|
||||
if (! s_request_queue.empty()) {
|
||||
if (! s_request_queue.empty())
|
||||
{
|
||||
result = s_request_queue.front();
|
||||
s_request_queue.pop();
|
||||
}
|
||||
|
@ -93,7 +103,8 @@ static ThreadedRequest_t *dequeue_request(void) {
|
|||
}
|
||||
|
||||
/* The function that does thread work. */
|
||||
static void *iothread_worker(void *threadPtr) {
|
||||
static void *iothread_worker(void *threadPtr)
|
||||
{
|
||||
assert(threadPtr != NULL);
|
||||
struct WorkerThread_t *thread = (struct WorkerThread_t *)threadPtr;
|
||||
|
||||
|
@ -101,7 +112,8 @@ static void *iothread_worker(void *threadPtr) {
|
|||
struct ThreadedRequest_t *req = dequeue_request();
|
||||
|
||||
/* Run the handler and store the result */
|
||||
if (req) {
|
||||
if (req)
|
||||
{
|
||||
req->handlerResult = req->handler(req->context);
|
||||
}
|
||||
|
||||
|
@ -113,9 +125,11 @@ static void *iothread_worker(void *threadPtr) {
|
|||
}
|
||||
|
||||
/* Spawn another thread if there's work to be done. */
|
||||
static void iothread_spawn_if_needed(void) {
|
||||
static void iothread_spawn_if_needed(void)
|
||||
{
|
||||
ASSERT_IS_LOCKED(s_request_queue_lock);
|
||||
if (! s_request_queue.empty() && s_active_thread_count < IO_MAX_THREADS) {
|
||||
if (! s_request_queue.empty() && s_active_thread_count < IO_MAX_THREADS)
|
||||
{
|
||||
struct WorkerThread_t *thread = next_vacant_thread_slot();
|
||||
assert(thread != NULL);
|
||||
|
||||
|
@ -126,12 +140,15 @@ static void iothread_spawn_if_needed(void) {
|
|||
|
||||
/* Spawn a thread. */
|
||||
int err;
|
||||
do {
|
||||
do
|
||||
{
|
||||
err = 0;
|
||||
if (pthread_create(&thread->thread, NULL, iothread_worker, thread)) {
|
||||
if (pthread_create(&thread->thread, NULL, iothread_worker, thread))
|
||||
{
|
||||
err = errno;
|
||||
}
|
||||
} while (err == EAGAIN);
|
||||
}
|
||||
while (err == EAGAIN);
|
||||
|
||||
/* Need better error handling - perhaps try again later. */
|
||||
assert(err == 0);
|
||||
|
@ -144,7 +161,8 @@ static void iothread_spawn_if_needed(void) {
|
|||
}
|
||||
}
|
||||
|
||||
int iothread_perform_base(int (*handler)(void *), void (*completionCallback)(void *, int), void *context) {
|
||||
int iothread_perform_base(int (*handler)(void *), void (*completionCallback)(void *, int), void *context)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
ASSERT_IS_NOT_FORKED_CHILD();
|
||||
iothread_init();
|
||||
|
@ -167,12 +185,14 @@ int iothread_perform_base(int (*handler)(void *), void (*completionCallback)(voi
|
|||
return 0;
|
||||
}
|
||||
|
||||
int iothread_port(void) {
|
||||
int iothread_port(void)
|
||||
{
|
||||
iothread_init();
|
||||
return s_read_pipe;
|
||||
}
|
||||
|
||||
void iothread_service_completion(void) {
|
||||
void iothread_service_completion(void)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
ThreadIndex_t threadIdx = (ThreadIndex_t)-1;
|
||||
VOMIT_ON_FAILURE(1 != read_loop(iothread_port(), &threadIdx, sizeof threadIdx));
|
||||
|
@ -190,7 +210,8 @@ void iothread_service_completion(void) {
|
|||
s_active_thread_count -= 1;
|
||||
|
||||
/* Handle the request */
|
||||
if (req) {
|
||||
if (req)
|
||||
{
|
||||
if (req->completionCallback)
|
||||
req->completionCallback(req->context, req->handlerResult);
|
||||
delete req;
|
||||
|
@ -202,7 +223,8 @@ void iothread_service_completion(void) {
|
|||
VOMIT_ON_FAILURE(pthread_mutex_unlock(&s_request_queue_lock));
|
||||
}
|
||||
|
||||
void iothread_drain_all(void) {
|
||||
void iothread_drain_all(void)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
ASSERT_IS_NOT_FORKED_CHILD();
|
||||
if (s_active_thread_count == 0)
|
||||
|
@ -212,7 +234,8 @@ void iothread_drain_all(void) {
|
|||
int thread_count = s_active_thread_count;
|
||||
double now = timef();
|
||||
#endif
|
||||
while (s_active_thread_count > 0) {
|
||||
while (s_active_thread_count > 0)
|
||||
{
|
||||
iothread_service_completion();
|
||||
}
|
||||
#if TIME_DRAIN
|
||||
|
|
|
@ -30,7 +30,8 @@ void iothread_drain_all(void);
|
|||
|
||||
/** Helper template */
|
||||
template<typename T>
|
||||
int iothread_perform(int (*handler)(T *), void (*completionCallback)(T *, int), T *context) {
|
||||
int iothread_perform(int (*handler)(T *), void (*completionCallback)(T *, int), T *context)
|
||||
{
|
||||
return iothread_perform_base((int (*)(void *))handler, (void (*)(void *, int))completionCallback, static_cast<void *>(context));
|
||||
}
|
||||
|
||||
|
|
20
kill.cpp
20
kill.cpp
|
@ -58,7 +58,8 @@ static wchar_t *cut_buffer=0;
|
|||
static int has_xsel()
|
||||
{
|
||||
static int res=-1;
|
||||
if (res < 0) {
|
||||
if (res < 0)
|
||||
{
|
||||
res = !! path_get_path(L"xsel", NULL);
|
||||
}
|
||||
|
||||
|
@ -94,7 +95,8 @@ void kill_add( const wcstring &str )
|
|||
else
|
||||
{
|
||||
/* This is for sending the kill to the X copy-and-paste buffer */
|
||||
if( !has_xsel() ) {
|
||||
if (!has_xsel())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -146,9 +148,12 @@ const wchar_t *kill_yank_rotate()
|
|||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
// Move the first element to the end
|
||||
if (kill_list.empty()) {
|
||||
if (kill_list.empty())
|
||||
{
|
||||
return NULL;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
kill_list.splice(kill_list.end(), kill_list, kill_list.begin());
|
||||
return kill_list.front().c_str();
|
||||
}
|
||||
|
@ -203,9 +208,12 @@ static void kill_check_x_buffer()
|
|||
const wchar_t *kill_yank()
|
||||
{
|
||||
kill_check_x_buffer();
|
||||
if (kill_list.empty()) {
|
||||
if (kill_list.empty())
|
||||
{
|
||||
return L"";
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return kill_list.front().c_str();
|
||||
}
|
||||
}
|
||||
|
|
93
lru.h
93
lru.h
|
@ -13,12 +13,17 @@
|
|||
#include "common.h"
|
||||
|
||||
/** A predicate to compare dereferenced pointers */
|
||||
struct dereference_less_t {
|
||||
struct dereference_less_t
|
||||
{
|
||||
template <typename ptr_t>
|
||||
bool operator()(ptr_t p1, ptr_t p2) const { return *p1 < *p2; }
|
||||
bool operator()(ptr_t p1, ptr_t p2) const
|
||||
{
|
||||
return *p1 < *p2;
|
||||
}
|
||||
};
|
||||
|
||||
class lru_node_t {
|
||||
class lru_node_t
|
||||
{
|
||||
template<class T> friend class lru_cache_t;
|
||||
|
||||
/** Our linked list pointer */
|
||||
|
@ -32,11 +37,15 @@ public:
|
|||
lru_node_t(const wcstring &pkey) : prev(NULL), next(NULL), key(pkey) { }
|
||||
|
||||
/** operator< for std::set */
|
||||
bool operator<(const lru_node_t &other) const { return key < other.key; }
|
||||
bool operator<(const lru_node_t &other) const
|
||||
{
|
||||
return key < other.key;
|
||||
}
|
||||
};
|
||||
|
||||
template<class node_type_t>
|
||||
class lru_cache_t {
|
||||
class lru_cache_t
|
||||
{
|
||||
private:
|
||||
|
||||
/** Max node count */
|
||||
|
@ -49,7 +58,8 @@ class lru_cache_t {
|
|||
typedef std::set<lru_node_t *, dereference_less_t> node_set_t;
|
||||
node_set_t node_set;
|
||||
|
||||
void promote_node(node_type_t *node) {
|
||||
void promote_node(node_type_t *node)
|
||||
{
|
||||
/* We should never promote the mouth */
|
||||
assert(node != &mouth);
|
||||
|
||||
|
@ -64,7 +74,8 @@ class lru_cache_t {
|
|||
mouth.next = node;
|
||||
}
|
||||
|
||||
void evict_node(node_type_t *condemned_node) {
|
||||
void evict_node(node_type_t *condemned_node)
|
||||
{
|
||||
/* We should never evict the mouth */
|
||||
assert(condemned_node != NULL && condemned_node != &mouth);
|
||||
|
||||
|
@ -80,7 +91,8 @@ class lru_cache_t {
|
|||
this->node_was_evicted(condemned_node);
|
||||
}
|
||||
|
||||
void evict_last_node(void) {
|
||||
void evict_last_node(void)
|
||||
{
|
||||
/* Simple */
|
||||
evict_node((node_type_t *)mouth.prev);
|
||||
}
|
||||
|
@ -96,7 +108,8 @@ class lru_cache_t {
|
|||
public:
|
||||
|
||||
/** Constructor */
|
||||
lru_cache_t(size_t max_size = 1024 ) : max_node_count(max_size), node_count(0), mouth(wcstring()) {
|
||||
lru_cache_t(size_t max_size = 1024) : max_node_count(max_size), node_count(0), mouth(wcstring())
|
||||
{
|
||||
/* Hook up the mouth to itself: a one node circularly linked list! */
|
||||
mouth.prev = mouth.next = &mouth;
|
||||
}
|
||||
|
@ -106,7 +119,8 @@ class lru_cache_t {
|
|||
|
||||
|
||||
/** Returns the node for a given key, or NULL */
|
||||
node_type_t *get_node(const wcstring &key) {
|
||||
node_type_t *get_node(const wcstring &key)
|
||||
{
|
||||
node_type_t *result = NULL;
|
||||
|
||||
/* Construct a fake node as our key */
|
||||
|
@ -116,7 +130,8 @@ class lru_cache_t {
|
|||
node_set_t::iterator iter = node_set.find(&node_key);
|
||||
|
||||
/* If we found a node, promote and return it */
|
||||
if (iter != node_set.end()) {
|
||||
if (iter != node_set.end())
|
||||
{
|
||||
result = static_cast<node_type_t*>(*iter);
|
||||
promote_node(result);
|
||||
}
|
||||
|
@ -124,7 +139,8 @@ class lru_cache_t {
|
|||
}
|
||||
|
||||
/** Evicts the node for a given key, returning true if a node was evicted. */
|
||||
bool evict_node(const wcstring &key) {
|
||||
bool evict_node(const wcstring &key)
|
||||
{
|
||||
/* Construct a fake node as our key */
|
||||
lru_node_t node_key(key);
|
||||
|
||||
|
@ -139,7 +155,8 @@ class lru_cache_t {
|
|||
}
|
||||
|
||||
/** Adds a node under the given key. Returns true if the node was added, false if the node was not because a node with that key is already in the set. */
|
||||
bool add_node(node_type_t *node) {
|
||||
bool add_node(node_type_t *node)
|
||||
{
|
||||
/* Add our node without eviction */
|
||||
if (! this->add_node_without_eviction(node))
|
||||
return false;
|
||||
|
@ -153,7 +170,8 @@ class lru_cache_t {
|
|||
}
|
||||
|
||||
/** Adds a node under the given key without triggering eviction. Returns true if the node was added, false if the node was not because a node with that key is already in the set. */
|
||||
bool add_node_without_eviction(node_type_t *node) {
|
||||
bool add_node_without_eviction(node_type_t *node)
|
||||
{
|
||||
assert(node != NULL && node != &mouth);
|
||||
|
||||
/* Try inserting; return false if it was already in the set */
|
||||
|
@ -178,31 +196,56 @@ class lru_cache_t {
|
|||
}
|
||||
|
||||
/** Counts nodes */
|
||||
size_t size(void) {
|
||||
size_t size(void)
|
||||
{
|
||||
return node_count;
|
||||
}
|
||||
|
||||
/** Evicts all nodes */
|
||||
void evict_all_nodes(void) {
|
||||
while (node_count > 0) {
|
||||
void evict_all_nodes(void)
|
||||
{
|
||||
while (node_count > 0)
|
||||
{
|
||||
evict_last_node();
|
||||
}
|
||||
}
|
||||
|
||||
/** Iterator for walking nodes, from least recently used to most */
|
||||
class iterator {
|
||||
class iterator
|
||||
{
|
||||
lru_node_t *node;
|
||||
public:
|
||||
iterator(lru_node_t *val) : node(val) { }
|
||||
void operator++() { node = node->prev; }
|
||||
void operator++(int x) { node = node->prev; }
|
||||
bool operator==(const iterator &other) { return node == other.node; }
|
||||
bool operator!=(const iterator &other) { return !(*this == other); }
|
||||
node_type_t *operator*() { return static_cast<node_type_t *>(node); }
|
||||
void operator++()
|
||||
{
|
||||
node = node->prev;
|
||||
}
|
||||
void operator++(int x)
|
||||
{
|
||||
node = node->prev;
|
||||
}
|
||||
bool operator==(const iterator &other)
|
||||
{
|
||||
return node == other.node;
|
||||
}
|
||||
bool operator!=(const iterator &other)
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
node_type_t *operator*()
|
||||
{
|
||||
return static_cast<node_type_t *>(node);
|
||||
}
|
||||
};
|
||||
|
||||
iterator begin() { return iterator(mouth.prev); }
|
||||
iterator end() { return iterator(&mouth); }
|
||||
iterator begin()
|
||||
{
|
||||
return iterator(mouth.prev);
|
||||
}
|
||||
iterator end()
|
||||
{
|
||||
return iterator(&mouth);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
10
mimedb.cpp
10
mimedb.cpp
|
@ -383,7 +383,8 @@ static int append_filenames( string_list_t &list, const char *f, int all )
|
|||
if (result)
|
||||
{
|
||||
list.push_back(result);
|
||||
if ( !all ) {
|
||||
if (!all)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
@ -401,9 +402,12 @@ static std::string get_filename( char *f )
|
|||
string_list_t list;
|
||||
|
||||
append_filenames(list, f, 0);
|
||||
if (list.empty()) {
|
||||
if (list.empty())
|
||||
{
|
||||
return "";
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return list.back();
|
||||
}
|
||||
}
|
||||
|
|
114
output.cpp
114
output.cpp
|
@ -130,35 +130,46 @@ int (*output_get_writer())(char)
|
|||
return out;
|
||||
}
|
||||
|
||||
static bool term256_support_is_native(void) {
|
||||
static bool term256_support_is_native(void)
|
||||
{
|
||||
/* Return YES if we think the term256 support is "native" as opposed to forced. */
|
||||
return max_colors == 256;
|
||||
}
|
||||
|
||||
bool output_get_supports_term256() {
|
||||
bool output_get_supports_term256()
|
||||
{
|
||||
return support_term256;
|
||||
}
|
||||
|
||||
void output_set_supports_term256(bool val) {
|
||||
void output_set_supports_term256(bool val)
|
||||
{
|
||||
support_term256 = val;
|
||||
}
|
||||
|
||||
static unsigned char index_for_color(rgb_color_t c) {
|
||||
if (c.is_named() || ! output_get_supports_term256()) {
|
||||
static unsigned char index_for_color(rgb_color_t c)
|
||||
{
|
||||
if (c.is_named() || ! output_get_supports_term256())
|
||||
{
|
||||
return c.to_name_index();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return c.to_term256_index();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool write_color(char *todo, unsigned char idx, bool is_fg) {
|
||||
static bool write_color(char *todo, unsigned char idx, bool is_fg)
|
||||
{
|
||||
bool result = false;
|
||||
if (idx < 16 || term256_support_is_native()) {
|
||||
if (idx < 16 || term256_support_is_native())
|
||||
{
|
||||
/* Use tparm */
|
||||
writembs(tparm(todo, idx));
|
||||
result = true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We are attempting to bypass the term here. Generate the ANSI escape sequence ourself. */
|
||||
char stridx[128];
|
||||
format_long_safe(stridx, idx);
|
||||
|
@ -168,8 +179,10 @@ static bool write_color(char *todo, unsigned char idx, bool is_fg) {
|
|||
strcat(buff, "m");
|
||||
|
||||
int (*writer)(char) = output_get_writer();
|
||||
if (writer) {
|
||||
for (size_t i=0; buff[i]; i++) {
|
||||
if (writer)
|
||||
{
|
||||
for (size_t i=0; buff[i]; i++)
|
||||
{
|
||||
writer(buff[i]);
|
||||
}
|
||||
}
|
||||
|
@ -179,22 +192,34 @@ static bool write_color(char *todo, unsigned char idx, bool is_fg) {
|
|||
return result;
|
||||
}
|
||||
|
||||
static bool write_foreground_color(unsigned char idx) {
|
||||
if (set_a_foreground && set_a_foreground[0]) {
|
||||
static bool write_foreground_color(unsigned char idx)
|
||||
{
|
||||
if (set_a_foreground && set_a_foreground[0])
|
||||
{
|
||||
return write_color(set_a_foreground, idx, true);
|
||||
} else if (set_foreground && set_foreground[0]) {
|
||||
}
|
||||
else if (set_foreground && set_foreground[0])
|
||||
{
|
||||
return write_color(set_foreground, idx, true);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool write_background_color(unsigned char idx) {
|
||||
if (set_a_background && set_a_background[0]) {
|
||||
static bool write_background_color(unsigned char idx)
|
||||
{
|
||||
if (set_a_background && set_a_background[0])
|
||||
{
|
||||
return write_color(set_a_background, idx, false);
|
||||
} else if (set_background && set_background[0]) {
|
||||
}
|
||||
else if (set_background && set_background[0])
|
||||
{
|
||||
return write_color(set_background, idx, false);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -566,7 +591,8 @@ int write_escaped_str( const wchar_t *str, int max_len )
|
|||
}
|
||||
|
||||
|
||||
int output_color_code( const wcstring &val, bool is_background ) {
|
||||
int output_color_code(const wcstring &val, bool is_background)
|
||||
{
|
||||
size_t i;
|
||||
int color=FISH_COLOR_NORMAL;
|
||||
int is_bold=0;
|
||||
|
@ -578,17 +604,22 @@ int output_color_code( const wcstring &val, bool is_background ) {
|
|||
wcstring_list_t el;
|
||||
tokenize_variable_array(val, el);
|
||||
|
||||
for(size_t j=0; j < el.size(); j++ ) {
|
||||
for (size_t j=0; j < el.size(); j++)
|
||||
{
|
||||
const wcstring &next = el.at(j);
|
||||
wcstring color_name;
|
||||
if (is_background) {
|
||||
if (is_background)
|
||||
{
|
||||
// look for something like "--background=red"
|
||||
const wcstring prefix = L"--background=";
|
||||
if (string_prefixes_string(prefix, next)) {
|
||||
if (string_prefixes_string(prefix, next))
|
||||
{
|
||||
color_name = wcstring(next, prefix.size());
|
||||
}
|
||||
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (next == L"--bold" || next == L"-o")
|
||||
is_bold = true;
|
||||
else if (next == L"--underline" || next == L"-u")
|
||||
|
@ -597,7 +628,8 @@ int output_color_code( const wcstring &val, bool is_background ) {
|
|||
color_name = next;
|
||||
}
|
||||
|
||||
if (! color_name.empty()) {
|
||||
if (! color_name.empty())
|
||||
{
|
||||
for (i=0; i<COLORS; i++)
|
||||
{
|
||||
if (wcscasecmp(col[i], color_name.c_str()) == 0)
|
||||
|
@ -613,7 +645,8 @@ int output_color_code( const wcstring &val, bool is_background ) {
|
|||
return color | (is_bold?FISH_COLOR_BOLD:0) | (is_underline?FISH_COLOR_UNDERLINE:0);
|
||||
}
|
||||
|
||||
rgb_color_t parse_color( const wcstring &val, bool is_background ) {
|
||||
rgb_color_t parse_color(const wcstring &val, bool is_background)
|
||||
{
|
||||
int is_bold=0;
|
||||
int is_underline=0;
|
||||
|
||||
|
@ -622,16 +655,21 @@ rgb_color_t parse_color( const wcstring &val, bool is_background ) {
|
|||
wcstring_list_t el;
|
||||
tokenize_variable_array(val, el);
|
||||
|
||||
for(size_t j=0; j < el.size(); j++ ) {
|
||||
for (size_t j=0; j < el.size(); j++)
|
||||
{
|
||||
const wcstring &next = el.at(j);
|
||||
wcstring color_name;
|
||||
if (is_background) {
|
||||
if (is_background)
|
||||
{
|
||||
// look for something like "--background=red"
|
||||
const wcstring prefix = L"--background=";
|
||||
if (string_prefixes_string(prefix, next)) {
|
||||
if (string_prefixes_string(prefix, next))
|
||||
{
|
||||
color_name = wcstring(next, prefix.size());
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (next == L"--bold" || next == L"-o")
|
||||
is_bold = true;
|
||||
else if (next == L"--underline" || next == L"-u")
|
||||
|
@ -640,9 +678,11 @@ rgb_color_t parse_color( const wcstring &val, bool is_background ) {
|
|||
color_name = next;
|
||||
}
|
||||
|
||||
if (! color_name.empty()) {
|
||||
if (! color_name.empty())
|
||||
{
|
||||
rgb_color_t color = rgb_color_t(color_name);
|
||||
if (! color.is_none()) {
|
||||
if (! color.is_none())
|
||||
{
|
||||
candidates.push_back(color);
|
||||
}
|
||||
}
|
||||
|
@ -650,7 +690,8 @@ rgb_color_t parse_color( const wcstring &val, bool is_background ) {
|
|||
|
||||
// Pick the best candidate
|
||||
rgb_color_t first_rgb = rgb_color_t::none(), first_named = rgb_color_t::none();
|
||||
for (size_t i=0; i < candidates.size(); i++) {
|
||||
for (size_t i=0; i < candidates.size(); i++)
|
||||
{
|
||||
const rgb_color_t &color = candidates.at(i);
|
||||
if (color.is_rgb() && first_rgb.is_none())
|
||||
first_rgb = color;
|
||||
|
@ -660,9 +701,12 @@ rgb_color_t parse_color( const wcstring &val, bool is_background ) {
|
|||
|
||||
// If we have both RGB and named colors, then prefer rgb if term256 is supported
|
||||
rgb_color_t result;
|
||||
if ((!first_rgb.is_none() && output_get_supports_term256()) || first_named.is_none()) {
|
||||
if ((!first_rgb.is_none() && output_get_supports_term256()) || first_named.is_none())
|
||||
{
|
||||
result = first_rgb;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
result = first_named;
|
||||
}
|
||||
|
||||
|
|
19
parser.cpp
19
parser.cpp
|
@ -601,7 +601,8 @@ static const wchar_t *parser_find_end( const wchar_t * buff )
|
|||
}
|
||||
|
||||
tok_destroy(&tok);
|
||||
if(!count && !error){
|
||||
if (!count && !error)
|
||||
{
|
||||
|
||||
return buff+mark;
|
||||
}
|
||||
|
@ -1261,10 +1262,13 @@ job_t *parser_t::job_create(void)
|
|||
bool parser_t::job_remove(job_t *j)
|
||||
{
|
||||
job_list_t::iterator iter = std::find(my_job_list.begin(), my_job_list.end(), j);
|
||||
if (iter != my_job_list.end()) {
|
||||
if (iter != my_job_list.end())
|
||||
{
|
||||
my_job_list.erase(iter);
|
||||
return true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
debug(1, _(L"Job inconsistency"));
|
||||
sanity_lose();
|
||||
return false;
|
||||
|
@ -1284,7 +1288,8 @@ job_t *parser_t::job_get(job_id_t id)
|
|||
{
|
||||
job_iterator_t jobs(my_job_list);
|
||||
job_t *job;
|
||||
while ((job = jobs.next())) {
|
||||
while ((job = jobs.next()))
|
||||
{
|
||||
if (id <= 0 || job->job_id == id)
|
||||
return job;
|
||||
}
|
||||
|
@ -1295,7 +1300,8 @@ job_t *parser_t::job_get_from_pid( int pid )
|
|||
{
|
||||
job_iterator_t jobs;
|
||||
job_t *job;
|
||||
while ((job = jobs.next())) {
|
||||
while ((job = jobs.next()))
|
||||
{
|
||||
if (job->pgid == pid)
|
||||
return job;
|
||||
}
|
||||
|
@ -3128,7 +3134,8 @@ int parser_t::test( const wchar_t * buff,
|
|||
{
|
||||
if (count >= BLOCK_MAX_COUNT)
|
||||
{
|
||||
if (out) {
|
||||
if (out)
|
||||
{
|
||||
error(SYNTAX_ERROR,
|
||||
tok_get_pos(&tok),
|
||||
BLOCK_ERR_MSG);
|
||||
|
|
30
parser.h
30
parser.h
|
@ -34,8 +34,10 @@ struct event_blockage_t
|
|||
|
||||
typedef std::list<event_blockage_t> event_blockage_list_t;
|
||||
|
||||
inline bool event_block_list_blocks_type(const event_blockage_list_t &ebls, int type) {
|
||||
for (event_blockage_list_t::const_iterator iter = ebls.begin(); iter != ebls.end(); ++iter) {
|
||||
inline bool event_block_list_blocks_type(const event_blockage_list_t &ebls, int type)
|
||||
{
|
||||
for (event_blockage_list_t::const_iterator iter = ebls.begin(); iter != ebls.end(); ++iter)
|
||||
{
|
||||
if (iter->typemask & (1<<EVENT_ANY))
|
||||
return true;
|
||||
if (iter->typemask & (1<<type))
|
||||
|
@ -81,10 +83,16 @@ struct block_t
|
|||
bool made_fake;
|
||||
|
||||
public:
|
||||
block_type_t type() const { return this->made_fake ? FAKE : this->block_type; }
|
||||
block_type_t type() const
|
||||
{
|
||||
return this->made_fake ? FAKE : this->block_type;
|
||||
}
|
||||
|
||||
/** Mark a block as fake; this is used by the return statement. */
|
||||
void mark_as_fake() { this->made_fake = true; }
|
||||
void mark_as_fake()
|
||||
{
|
||||
this->made_fake = true;
|
||||
}
|
||||
|
||||
bool skip; /**< Whether execution of the commands in this block should be skipped */
|
||||
bool had_command; /**< Set to non-zero once a command has been executed in this block */
|
||||
|
@ -254,7 +262,8 @@ enum parser_error
|
|||
CMDSUBST_ERROR,
|
||||
};
|
||||
|
||||
enum parser_type_t {
|
||||
enum parser_type_t
|
||||
{
|
||||
PARSER_TYPE_NONE,
|
||||
PARSER_TYPE_GENERAL,
|
||||
PARSER_TYPE_FUNCTIONS_ONLY,
|
||||
|
@ -262,7 +271,8 @@ enum parser_type_t {
|
|||
PARSER_TYPE_ERRORS_ONLY
|
||||
};
|
||||
|
||||
struct profile_item_t {
|
||||
struct profile_item_t
|
||||
{
|
||||
/**
|
||||
Time spent executing the specified command, including parse time for nested blocks.
|
||||
*/
|
||||
|
@ -287,7 +297,8 @@ struct profile_item_t {
|
|||
|
||||
struct tokenizer;
|
||||
|
||||
class parser_t {
|
||||
class parser_t
|
||||
{
|
||||
private:
|
||||
enum parser_type_t parser_type;
|
||||
std::vector<block_t> blocks;
|
||||
|
@ -432,7 +443,10 @@ class parser_t {
|
|||
const wchar_t *get_buffer() const;
|
||||
|
||||
/** Get the list of jobs */
|
||||
job_list_t &job_list() { return my_job_list; }
|
||||
job_list_t &job_list()
|
||||
{
|
||||
return my_job_list;
|
||||
}
|
||||
|
||||
/** Pushes the block. pop_block will call delete on it. */
|
||||
void push_block(block_t *newv);
|
||||
|
|
|
@ -16,11 +16,16 @@ Functions having to do with parser keywords, like testing if a function is a blo
|
|||
|
||||
bool parser_keywords_is_switch(const wcstring &cmd)
|
||||
{
|
||||
if (cmd == L"--") {
|
||||
if (cmd == L"--")
|
||||
{
|
||||
return ARG_SKIP;
|
||||
} else if (! cmd.empty() && cmd.at(0) == L'-') {
|
||||
}
|
||||
else if (! cmd.empty() && cmd.at(0) == L'-')
|
||||
{
|
||||
return ARG_SWITCH;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return ARG_NON_SWITCH;
|
||||
}
|
||||
}
|
||||
|
|
66
path.cpp
66
path.cpp
|
@ -172,7 +172,8 @@ bool path_get_cdpath_string(const wcstring &dir_str, wcstring &result, const env
|
|||
|
||||
// Respect CDPATH
|
||||
env_var_t cdpath = env_get_string(L"CDPATH");
|
||||
if (! cdpath.missing_or_empty()) {
|
||||
if (! cdpath.missing_or_empty())
|
||||
{
|
||||
path = cdpath.c_str();
|
||||
}
|
||||
|
||||
|
@ -232,19 +233,24 @@ bool path_get_cdpath(const wcstring &dir, wcstring *out, const wchar_t *wd, cons
|
|||
}
|
||||
|
||||
wcstring_list_t paths;
|
||||
if (dir.at(0) == L'/') {
|
||||
if (dir.at(0) == L'/')
|
||||
{
|
||||
/* Absolute path */
|
||||
paths.push_back(dir);
|
||||
} else if (string_prefixes_string(L"./", dir) ||
|
||||
}
|
||||
else if (string_prefixes_string(L"./", dir) ||
|
||||
string_prefixes_string(L"../", dir) ||
|
||||
dir == L"." || dir == L"..") {
|
||||
dir == L"." || dir == L"..")
|
||||
{
|
||||
/* Path is relative to the working directory */
|
||||
wcstring path;
|
||||
if (wd)
|
||||
path.append(wd);
|
||||
path.append(dir);
|
||||
paths.push_back(path);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Respect CDPATH
|
||||
env_var_t path = env_vars.get(L"CDPATH");
|
||||
if (path.missing_or_empty())
|
||||
|
@ -255,7 +261,8 @@ bool path_get_cdpath(const wcstring &dir, wcstring *out, const wchar_t *wd, cons
|
|||
while (tokenizer.next(nxt_path))
|
||||
{
|
||||
|
||||
if (nxt_path == L"." && wd != NULL) {
|
||||
if (nxt_path == L"." && wd != NULL)
|
||||
{
|
||||
// nxt_path is just '.', and we have a working directory, so use the wd instead
|
||||
// TODO: if nxt_path starts with ./ we need to replace the . with the wd
|
||||
nxt_path = wd;
|
||||
|
@ -274,7 +281,8 @@ bool path_get_cdpath(const wcstring &dir, wcstring *out, const wchar_t *wd, cons
|
|||
}
|
||||
|
||||
bool success = false;
|
||||
for (wcstring_list_t::const_iterator iter = paths.begin(); iter != paths.end(); ++iter) {
|
||||
for (wcstring_list_t::const_iterator iter = paths.begin(); iter != paths.end(); ++iter)
|
||||
{
|
||||
struct stat buf;
|
||||
const wcstring &dir = *iter;
|
||||
if (wstat(dir, &buf) == 0)
|
||||
|
@ -371,13 +379,16 @@ void path_make_canonical( wcstring &path )
|
|||
|
||||
/* Remove double slashes */
|
||||
size_t size;
|
||||
do {
|
||||
do
|
||||
{
|
||||
size = path.size();
|
||||
replace_all(path, L"//", L"/");
|
||||
} while (path.size() != size);
|
||||
}
|
||||
while (path.size() != size);
|
||||
|
||||
/* Remove trailing slashes, except don't remove the first one */
|
||||
while (size-- > 1) {
|
||||
while (size-- > 1)
|
||||
{
|
||||
if (path.at(size) != L'/')
|
||||
break;
|
||||
}
|
||||
|
@ -389,41 +400,56 @@ bool path_is_valid(const wcstring &path, const wcstring &working_directory)
|
|||
{
|
||||
bool path_is_valid;
|
||||
/* Some special paths are always valid */
|
||||
if (path.empty()) {
|
||||
if (path.empty())
|
||||
{
|
||||
path_is_valid = false;
|
||||
} else if (path == L"." || path == L"./") {
|
||||
}
|
||||
else if (path == L"." || path == L"./")
|
||||
{
|
||||
path_is_valid = true;
|
||||
} else if (path == L".." || path == L"../") {
|
||||
}
|
||||
else if (path == L".." || path == L"../")
|
||||
{
|
||||
path_is_valid = (! working_directory.empty() && working_directory != L"/");
|
||||
} else if (path.at(0) != '/') {
|
||||
}
|
||||
else if (path.at(0) != '/')
|
||||
{
|
||||
/* Prepend the working directory. Note that we know path is not empty here. */
|
||||
wcstring tmp = working_directory;
|
||||
tmp.append(path);
|
||||
path_is_valid = (0 == waccess(tmp, F_OK));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Simple check */
|
||||
path_is_valid = (0 == waccess(path, F_OK));
|
||||
}
|
||||
return path_is_valid;
|
||||
}
|
||||
|
||||
bool paths_are_same_file(const wcstring &path1, const wcstring &path2) {
|
||||
bool paths_are_same_file(const wcstring &path1, const wcstring &path2)
|
||||
{
|
||||
if (path1 == path2)
|
||||
return true;
|
||||
|
||||
struct stat s1, s2;
|
||||
if (wstat(path1, &s1) == 0 && wstat(path2, &s2) == 0) {
|
||||
if (wstat(path1, &s1) == 0 && wstat(path2, &s2) == 0)
|
||||
{
|
||||
return s1.st_ino == s2.st_ino && s1.st_dev == s2.st_dev;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
wcstring get_working_directory(void) {
|
||||
wcstring get_working_directory(void)
|
||||
{
|
||||
wcstring wd = L"./";
|
||||
wchar_t dir_path[4096];
|
||||
const wchar_t *cwd = wgetcwd(dir_path, 4096);
|
||||
if (cwd) {
|
||||
if (cwd)
|
||||
{
|
||||
wd = cwd;
|
||||
/* Make sure the working directory ends with a slash */
|
||||
if (! wd.empty() && wd.at(wd.size() - 1) != L'/')
|
||||
|
|
12
postfork.cpp
12
postfork.cpp
|
@ -319,7 +319,8 @@ pid_t execute_fork(bool wait_for_threads_to_die)
|
|||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
|
||||
if (wait_for_threads_to_die) {
|
||||
if (wait_for_threads_to_die)
|
||||
{
|
||||
/* Make sure we have no outstanding threads before we fork. This is a pretty sketchy thing to do here, both because exec.cpp shouldn't have to know about iothreads, and because the completion handlers may do unexpected things. */
|
||||
iothread_drain_all();
|
||||
}
|
||||
|
@ -366,11 +367,13 @@ pid_t execute_fork(bool wait_for_threads_to_die)
|
|||
bool fork_actions_make_spawn_properties(posix_spawnattr_t *attr, posix_spawn_file_actions_t *actions, job_t *j, process_t *p)
|
||||
{
|
||||
/* Initialize the output */
|
||||
if (posix_spawnattr_init(attr) != 0) {
|
||||
if (posix_spawnattr_init(attr) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (posix_spawn_file_actions_init(actions) != 0) {
|
||||
if (posix_spawn_file_actions_init(actions) != 0)
|
||||
{
|
||||
posix_spawnattr_destroy(attr);
|
||||
return false;
|
||||
}
|
||||
|
@ -503,7 +506,8 @@ bool fork_actions_make_spawn_properties(posix_spawnattr_t *attr, posix_spawn_fil
|
|||
}
|
||||
|
||||
/* Clean up on error */
|
||||
if (err) {
|
||||
if (err)
|
||||
{
|
||||
posix_spawnattr_destroy(attr);
|
||||
posix_spawn_file_actions_destroy(actions);
|
||||
}
|
||||
|
|
18
proc.cpp
18
proc.cpp
|
@ -106,12 +106,14 @@ void job_iterator_t::reset()
|
|||
this->end = job_list->end();
|
||||
}
|
||||
|
||||
job_iterator_t::job_iterator_t(job_list_t &jobs) : job_list(&jobs) {
|
||||
job_iterator_t::job_iterator_t(job_list_t &jobs) : job_list(&jobs)
|
||||
{
|
||||
this->reset();
|
||||
}
|
||||
|
||||
|
||||
job_iterator_t::job_iterator_t() : job_list(&parser_t::principal_parser().job_list()) {
|
||||
job_iterator_t::job_iterator_t() : job_list(&parser_t::principal_parser().job_list())
|
||||
{
|
||||
this->reset();
|
||||
}
|
||||
|
||||
|
@ -129,17 +131,20 @@ static int is_interactive = -1;
|
|||
|
||||
static bool proc_had_barrier = false;
|
||||
|
||||
int get_is_interactive(void) {
|
||||
int get_is_interactive(void)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
return is_interactive;
|
||||
}
|
||||
|
||||
bool get_proc_had_barrier() {
|
||||
bool get_proc_had_barrier()
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
return proc_had_barrier;
|
||||
}
|
||||
|
||||
void set_proc_had_barrier(bool flag) {
|
||||
void set_proc_had_barrier(bool flag)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
proc_had_barrier = flag;
|
||||
}
|
||||
|
@ -245,7 +250,8 @@ void release_job_id(job_id_t jid)
|
|||
|
||||
/* Clear it and then resize the vector to eliminate unused trailing job IDs */
|
||||
consumed_job_ids.at(slot) = false;
|
||||
while (count--) {
|
||||
while (count--)
|
||||
{
|
||||
if (consumed_job_ids.at(count))
|
||||
break;
|
||||
}
|
||||
|
|
57
proc.h
57
proc.h
|
@ -153,30 +153,40 @@ class process_t
|
|||
|
||||
|
||||
/** Sets argv */
|
||||
void set_argv(const wcstring_list_t &argv) {
|
||||
void set_argv(const wcstring_list_t &argv)
|
||||
{
|
||||
argv_array.set(argv);
|
||||
argv0_narrow.set(argv.empty() ? L"" : argv[0]);
|
||||
}
|
||||
|
||||
/** Returns argv */
|
||||
const wchar_t * const *get_argv(void) const { return argv_array.get(); }
|
||||
const null_terminated_array_t<wchar_t> &get_argv_array(void) const { return argv_array; }
|
||||
const wchar_t * const *get_argv(void) const
|
||||
{
|
||||
return argv_array.get();
|
||||
}
|
||||
const null_terminated_array_t<wchar_t> &get_argv_array(void) const
|
||||
{
|
||||
return argv_array;
|
||||
}
|
||||
|
||||
/** Returns argv[idx] */
|
||||
const wchar_t *argv(size_t idx) const {
|
||||
const wchar_t *argv(size_t idx) const
|
||||
{
|
||||
const wchar_t * const *argv = argv_array.get();
|
||||
assert(argv != NULL);
|
||||
return argv[idx];
|
||||
}
|
||||
|
||||
/** Returns argv[0], or NULL */
|
||||
const wchar_t *argv0(void) const {
|
||||
const wchar_t *argv0(void) const
|
||||
{
|
||||
const wchar_t * const *argv = argv_array.get();
|
||||
return argv ? argv[0] : NULL;
|
||||
}
|
||||
|
||||
/** Returns argv[0] as a char * */
|
||||
const char *argv0_cstr(void) const {
|
||||
const char *argv0_cstr(void) const
|
||||
{
|
||||
return argv0_narrow.get();
|
||||
}
|
||||
|
||||
|
@ -215,7 +225,8 @@ class process_t
|
|||
};
|
||||
|
||||
/* Constants for the flag variable in the job struct */
|
||||
enum {
|
||||
enum
|
||||
{
|
||||
/** true if user was told about stopped job */
|
||||
JOB_NOTIFIED = 1 << 0,
|
||||
|
||||
|
@ -281,19 +292,32 @@ class job_t
|
|||
~job_t();
|
||||
|
||||
/** Returns whether the command is empty. */
|
||||
bool command_is_empty() const { return command_str.empty(); }
|
||||
bool command_is_empty() const
|
||||
{
|
||||
return command_str.empty();
|
||||
}
|
||||
|
||||
/** Returns the command as a wchar_t *. */
|
||||
const wchar_t *command_wcstr() const { return command_str.c_str(); }
|
||||
const wchar_t *command_wcstr() const
|
||||
{
|
||||
return command_str.c_str();
|
||||
}
|
||||
|
||||
/** Returns the command */
|
||||
const wcstring &command() const { return command_str; }
|
||||
const wcstring &command() const
|
||||
{
|
||||
return command_str;
|
||||
}
|
||||
|
||||
/** Returns the command as a char *. */
|
||||
const char *command_cstr() const { return command_narrow.get(); }
|
||||
const char *command_cstr() const
|
||||
{
|
||||
return command_narrow.get();
|
||||
}
|
||||
|
||||
/** Sets the command */
|
||||
void set_command(const wcstring &cmd) {
|
||||
void set_command(const wcstring &cmd)
|
||||
{
|
||||
command_str = cmd;
|
||||
command_narrow.set(cmd);
|
||||
}
|
||||
|
@ -371,16 +395,19 @@ bool job_list_is_empty(void);
|
|||
/** A class to aid iteration over jobs list.
|
||||
Note this is used from a signal handler, so it must be careful to not allocate memory.
|
||||
*/
|
||||
class job_iterator_t {
|
||||
class job_iterator_t
|
||||
{
|
||||
job_list_t * const job_list;
|
||||
job_list_t::iterator current, end;
|
||||
public:
|
||||
|
||||
void reset(void);
|
||||
|
||||
job_t *next() {
|
||||
job_t *next()
|
||||
{
|
||||
job_t *job = NULL;
|
||||
if (current != end) {
|
||||
if (current != end)
|
||||
{
|
||||
job = *current;
|
||||
++current;
|
||||
}
|
||||
|
|
181
reader.cpp
181
reader.cpp
|
@ -240,7 +240,10 @@ class reader_data_t
|
|||
size_t search_pos;
|
||||
|
||||
/** Length of the command */
|
||||
size_t command_length() const { return command_line.size(); }
|
||||
size_t command_length() const
|
||||
{
|
||||
return command_line.size();
|
||||
}
|
||||
|
||||
/** Do what we need to do whenever our command line changes */
|
||||
void command_line_changed(void);
|
||||
|
@ -509,7 +512,8 @@ static void reader_kill( size_t begin_idx, size_t length, int mode, int newv )
|
|||
kill_replace(old, data->kill_item);
|
||||
}
|
||||
|
||||
if( data->buff_pos > begin_idx ) {
|
||||
if (data->buff_pos > begin_idx)
|
||||
{
|
||||
/* Move the buff position back by the number of characters we deleted, but don't go past buff_pos */
|
||||
size_t backtrack = mini(data->buff_pos - begin_idx, length);
|
||||
data->buff_pos -= backtrack;
|
||||
|
@ -557,7 +561,8 @@ void reader_pop_current_filename()
|
|||
|
||||
|
||||
/** Make sure buffers are large enough to hold the current string length */
|
||||
void reader_data_t::command_line_changed() {
|
||||
void reader_data_t::command_line_changed()
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
size_t len = command_length();
|
||||
|
||||
|
@ -745,25 +750,31 @@ void reader_exit( int do_exit, int forced )
|
|||
|
||||
void reader_repaint_needed()
|
||||
{
|
||||
if (data) {
|
||||
if (data)
|
||||
{
|
||||
data->repaint_needed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void reader_repaint_if_needed() {
|
||||
if (data && data->screen_reset_needed) {
|
||||
void reader_repaint_if_needed()
|
||||
{
|
||||
if (data && data->screen_reset_needed)
|
||||
{
|
||||
s_reset(&data->screen, false);
|
||||
data->screen_reset_needed = false;
|
||||
}
|
||||
|
||||
if (data && data->repaint_needed) {
|
||||
if (data && data->repaint_needed)
|
||||
{
|
||||
reader_repaint();
|
||||
/* reader_repaint clears repaint_needed */
|
||||
}
|
||||
}
|
||||
|
||||
void reader_react_to_color_change() {
|
||||
if (data) {
|
||||
void reader_react_to_color_change()
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
data->repaint_needed = true;
|
||||
data->screen_reset_needed = true;
|
||||
}
|
||||
|
@ -782,11 +793,13 @@ static void remove_backward()
|
|||
|
||||
/* Fake composed character sequences by continuning to delete until we delete a character of width at least 1. */
|
||||
int width;
|
||||
do {
|
||||
do
|
||||
{
|
||||
data->buff_pos -= 1;
|
||||
width = fish_wcwidth(data->command_line.at(data->buff_pos));
|
||||
data->command_line.erase(data->buff_pos, 1);
|
||||
} while (width == 0 && data->buff_pos > 0);
|
||||
}
|
||||
while (width == 0 && data->buff_pos > 0);
|
||||
data->command_line_changed();
|
||||
data->suppress_autosuggestion = true;
|
||||
|
||||
|
@ -1096,7 +1109,8 @@ static void run_pager( const wcstring &prefix, int is_quoted, const std::vector<
|
|||
io_buffer_destroy(in);
|
||||
}
|
||||
|
||||
struct autosuggestion_context_t {
|
||||
struct autosuggestion_context_t
|
||||
{
|
||||
wcstring search_string;
|
||||
wcstring autosuggestion;
|
||||
size_t cursor_pos;
|
||||
|
@ -1123,27 +1137,32 @@ struct autosuggestion_context_t {
|
|||
}
|
||||
|
||||
/* The function run in the background thread to determine an autosuggestion */
|
||||
int threaded_autosuggest(void) {
|
||||
int threaded_autosuggest(void)
|
||||
{
|
||||
ASSERT_IS_BACKGROUND_THREAD();
|
||||
|
||||
/* If the main thread has moved on, skip all the work */
|
||||
if (generation_count != s_generation_count) {
|
||||
if (generation_count != s_generation_count)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Let's make sure we aren't using the empty string */
|
||||
if (search_string.empty()) {
|
||||
if (search_string.empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (searcher.go_backwards()) {
|
||||
while (searcher.go_backwards())
|
||||
{
|
||||
history_item_t item = searcher.current_item();
|
||||
|
||||
/* Skip items with newlines because they make terrible autosuggestions */
|
||||
if (item.str().find('\n') != wcstring::npos)
|
||||
continue;
|
||||
|
||||
if (autosuggest_validate_from_history(item, detector, working_directory, vars)) {
|
||||
if (autosuggest_validate_from_history(item, detector, working_directory, vars))
|
||||
{
|
||||
/* The command autosuggestion was handled specially, so we're done */
|
||||
this->autosuggestion = searcher.current_string();
|
||||
return 1;
|
||||
|
@ -1153,7 +1172,8 @@ struct autosuggestion_context_t {
|
|||
|
||||
/* Try handling a special command like cd */
|
||||
wcstring special_suggestion;
|
||||
if (autosuggest_suggest_special(search_string, working_directory, special_suggestion)) {
|
||||
if (autosuggest_suggest_special(search_string, working_directory, special_suggestion))
|
||||
{
|
||||
this->autosuggestion = special_suggestion;
|
||||
return 1;
|
||||
}
|
||||
|
@ -1174,7 +1194,8 @@ struct autosuggestion_context_t {
|
|||
/* Try normal completions */
|
||||
std::vector<completion_t> completions;
|
||||
complete(search_string, completions, COMPLETE_AUTOSUGGEST, &this->commands_to_load);
|
||||
if (! completions.empty()) {
|
||||
if (! completions.empty())
|
||||
{
|
||||
const completion_t &comp = completions.at(0);
|
||||
size_t cursor = this->cursor_pos;
|
||||
this->autosuggestion = completion_apply_to_command_line(comp.completion.c_str(), comp.flags, this->search_string, &cursor);
|
||||
|
@ -1185,11 +1206,13 @@ struct autosuggestion_context_t {
|
|||
}
|
||||
};
|
||||
|
||||
static int threaded_autosuggest(autosuggestion_context_t *ctx) {
|
||||
static int threaded_autosuggest(autosuggestion_context_t *ctx)
|
||||
{
|
||||
return ctx->threaded_autosuggest();
|
||||
}
|
||||
|
||||
static bool can_autosuggest(void) {
|
||||
static bool can_autosuggest(void)
|
||||
{
|
||||
/* We autosuggest if suppress_autosuggestion is not set, if we're not doing a history search, and our command line contains a non-whitespace character. */
|
||||
const wchar_t *whitespace = L" \t\r\n\v";
|
||||
return ! data->suppress_autosuggestion &&
|
||||
|
@ -1197,7 +1220,8 @@ static bool can_autosuggest(void) {
|
|||
data->command_line.find_first_not_of(whitespace) != wcstring::npos;
|
||||
}
|
||||
|
||||
static void autosuggest_completed(autosuggestion_context_t *ctx, int result) {
|
||||
static void autosuggest_completed(autosuggestion_context_t *ctx, int result)
|
||||
{
|
||||
|
||||
/* Extract the commands to load */
|
||||
wcstring_list_t commands_to_load;
|
||||
|
@ -1218,7 +1242,8 @@ static void autosuggest_completed(autosuggestion_context_t *ctx, int result) {
|
|||
if (result &&
|
||||
can_autosuggest() &&
|
||||
ctx->search_string == data->command_line &&
|
||||
string_prefixes_string_case_insensitive(ctx->search_string, ctx->autosuggestion)) {
|
||||
string_prefixes_string_case_insensitive(ctx->search_string, ctx->autosuggestion))
|
||||
{
|
||||
/* Autosuggestion is active and the search term has not changed, so we're good to go */
|
||||
data->autosuggestion = ctx->autosuggestion;
|
||||
sanity_check();
|
||||
|
@ -1228,29 +1253,35 @@ static void autosuggest_completed(autosuggestion_context_t *ctx, int result) {
|
|||
}
|
||||
|
||||
|
||||
static void update_autosuggestion(void) {
|
||||
static void update_autosuggestion(void)
|
||||
{
|
||||
/* Updates autosuggestion. We look for an autosuggestion if the command line is non-empty and if we're not doing a history search. */
|
||||
#if 0
|
||||
/* Old non-threaded mode */
|
||||
data->autosuggestion.clear();
|
||||
if (can_autosuggest()) {
|
||||
if (can_autosuggest())
|
||||
{
|
||||
history_search_t searcher = history_search_t(*data->history, data->command_line, HISTORY_SEARCH_TYPE_PREFIX);
|
||||
if (searcher.go_backwards()) {
|
||||
if (searcher.go_backwards())
|
||||
{
|
||||
data->autosuggestion = searcher.current_item().str();
|
||||
}
|
||||
}
|
||||
#else
|
||||
data->autosuggestion.clear();
|
||||
if (data->allow_autosuggestion && ! data->suppress_autosuggestion && ! data->command_line.empty() && data->history_search.is_at_end()) {
|
||||
if (data->allow_autosuggestion && ! data->suppress_autosuggestion && ! data->command_line.empty() && data->history_search.is_at_end())
|
||||
{
|
||||
autosuggestion_context_t *ctx = new autosuggestion_context_t(data->history, data->command_line, data->buff_pos);
|
||||
iothread_perform(threaded_autosuggest, autosuggest_completed, ctx);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void accept_autosuggestion(void) {
|
||||
static void accept_autosuggestion(void)
|
||||
{
|
||||
/* Accept any autosuggestion by replacing the command line with it. */
|
||||
if (! data->autosuggestion.empty()) {
|
||||
if (! data->autosuggestion.empty())
|
||||
{
|
||||
/* Accept the autosuggestion */
|
||||
data->command_line = data->autosuggestion;
|
||||
data->buff_pos = data->command_line.size();
|
||||
|
@ -1833,7 +1864,8 @@ static void handle_token_history( int forward, int reset )
|
|||
/*
|
||||
Search for previous item that contains this substring
|
||||
*/
|
||||
if (data->history_search.go_backwards()) {
|
||||
if (data->history_search.go_backwards())
|
||||
{
|
||||
wcstring item = data->history_search.current_string();
|
||||
data->token_history_buff = data->history_search.current_string();
|
||||
}
|
||||
|
@ -1882,7 +1914,8 @@ static void handle_token_history( int forward, int reset )
|
|||
//debug( 3, L"ok pos" );
|
||||
|
||||
const wcstring last_tok = tok_last(&tok);
|
||||
if (find(data->search_prev.begin(), data->search_prev.end(), last_tok) == data->search_prev.end()) {
|
||||
if (find(data->search_prev.begin(), data->search_prev.end(), last_tok) == data->search_prev.end())
|
||||
{
|
||||
data->token_history_pos = tok_get_pos(&tok);
|
||||
str = wcsdup(tok_last(&tok));
|
||||
}
|
||||
|
@ -1914,7 +1947,8 @@ static void handle_token_history( int forward, int reset )
|
|||
/* Our state machine that implements "one word" movement or erasure. */
|
||||
class move_word_state_machine_t
|
||||
{
|
||||
enum {
|
||||
enum
|
||||
{
|
||||
s_whitespace,
|
||||
s_punctuation,
|
||||
s_alphanumeric_or_punctuation_except_slash,
|
||||
|
@ -1973,7 +2007,8 @@ class move_word_state_machine_t
|
|||
/foo/bar/baz/ -> /foo/bar/ -> /foo/ -> /
|
||||
echo --foo --bar -> echo --foo -> echo
|
||||
*/
|
||||
enum move_word_dir_t {
|
||||
enum move_word_dir_t
|
||||
{
|
||||
MOVE_DIR_LEFT,
|
||||
MOVE_DIR_RIGHT
|
||||
};
|
||||
|
@ -2039,7 +2074,8 @@ const wchar_t *reader_get_buffer(void)
|
|||
return data?data->command_line.c_str():NULL;
|
||||
}
|
||||
|
||||
history_t *reader_get_history(void) {
|
||||
history_t *reader_get_history(void)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
return data ? data->history : NULL;
|
||||
}
|
||||
|
@ -2085,23 +2121,36 @@ void set_env_cmd_duration(struct timeval *after, struct timeval *before)
|
|||
suseconds_t usecs = after->tv_usec - before->tv_usec;
|
||||
wchar_t buf[16];
|
||||
|
||||
if (after->tv_usec < before->tv_usec) {
|
||||
if (after->tv_usec < before->tv_usec)
|
||||
{
|
||||
usecs += 1000000;
|
||||
secs -= 1;
|
||||
}
|
||||
|
||||
if (secs < 1) {
|
||||
if (secs < 1)
|
||||
{
|
||||
env_remove(ENV_CMD_DURATION, 0);
|
||||
} else {
|
||||
if (secs < 10) { // 10 secs
|
||||
}
|
||||
else
|
||||
{
|
||||
if (secs < 10) // 10 secs
|
||||
{
|
||||
swprintf(buf, 16, L"%lu.%02us", secs, usecs / 10000);
|
||||
} else if (secs < 60) { // 1 min
|
||||
}
|
||||
else if (secs < 60) // 1 min
|
||||
{
|
||||
swprintf(buf, 16, L"%lu.%01us", secs, usecs / 100000);
|
||||
} else if (secs < 600) { // 10 mins
|
||||
}
|
||||
else if (secs < 600) // 10 mins
|
||||
{
|
||||
swprintf(buf, 16, L"%lum %lu.%01us", secs / 60, secs % 60, usecs / 100000);
|
||||
} else if (secs < 5400) { // 1.5 hours
|
||||
}
|
||||
else if (secs < 5400) // 1.5 hours
|
||||
{
|
||||
swprintf(buf, 16, L"%lum %lus", secs / 60, secs % 60);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
swprintf(buf, 16, L"%.1fh", secs / 3600.0);
|
||||
}
|
||||
env_set(ENV_CMD_DURATION, buf, ENV_EXPORT);
|
||||
|
@ -2283,7 +2332,8 @@ void reader_import_history_if_necessary(void)
|
|||
}
|
||||
|
||||
/** A class as the context pointer for a background (threaded) highlight operation. */
|
||||
class background_highlight_context_t {
|
||||
class background_highlight_context_t
|
||||
{
|
||||
public:
|
||||
/** The string to highlight */
|
||||
const wcstring string_to_highlight;
|
||||
|
@ -2317,7 +2367,8 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
int threaded_highlight() {
|
||||
int threaded_highlight()
|
||||
{
|
||||
if (generation_count != s_generation_count)
|
||||
{
|
||||
// The gen count has changed, so don't do anything
|
||||
|
@ -2332,7 +2383,8 @@ public:
|
|||
};
|
||||
|
||||
/* Called to set the highlight flag for search results */
|
||||
static void highlight_search(void) {
|
||||
static void highlight_search(void)
|
||||
{
|
||||
if (! data->search_buff.empty() && ! data->history_search.is_at_end())
|
||||
{
|
||||
const wchar_t *buff = data->command_line.c_str();
|
||||
|
@ -2349,9 +2401,11 @@ static void highlight_search(void) {
|
|||
}
|
||||
}
|
||||
|
||||
static void highlight_complete(background_highlight_context_t *ctx, int result) {
|
||||
static void highlight_complete(background_highlight_context_t *ctx, int result)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
if (ctx->string_to_highlight == data->command_line) {
|
||||
if (ctx->string_to_highlight == data->command_line)
|
||||
{
|
||||
/* The data hasn't changed, so swap in our colors. The colors may not have changed, so do nothing if they have not. */
|
||||
assert(ctx->colors.size() == data->command_length());
|
||||
if (data->colors != ctx->colors)
|
||||
|
@ -2371,7 +2425,8 @@ static void highlight_complete(background_highlight_context_t *ctx, int result)
|
|||
delete ctx;
|
||||
}
|
||||
|
||||
static int threaded_highlight(background_highlight_context_t *ctx) {
|
||||
static int threaded_highlight(background_highlight_context_t *ctx)
|
||||
{
|
||||
return ctx->threaded_highlight();
|
||||
}
|
||||
|
||||
|
@ -2395,9 +2450,12 @@ static void reader_super_highlight_me_plenty( size_t match_highlight_pos )
|
|||
|
||||
/* Here's a hack. Check to see if our autosuggestion still applies; if so, don't recompute it. Since the autosuggestion computation is asynchronous, this avoids "flashing" as you type into the autosuggestion. */
|
||||
const wcstring &cmd = data->command_line, &suggest = data->autosuggestion;
|
||||
if (can_autosuggest() && ! suggest.empty() && string_prefixes_string_case_insensitive(cmd, suggest)) {
|
||||
if (can_autosuggest() && ! suggest.empty() && string_prefixes_string_case_insensitive(cmd, suggest))
|
||||
{
|
||||
/* The autosuggestion is still reasonable, so do nothing */
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
update_autosuggestion();
|
||||
}
|
||||
}
|
||||
|
@ -3004,7 +3062,8 @@ const wchar_t *reader_readline()
|
|||
*/
|
||||
if (! data->command_line.empty())
|
||||
{
|
||||
if (data->history) {
|
||||
if (data->history)
|
||||
{
|
||||
data->history->add_with_file_detection(data->command_line);
|
||||
}
|
||||
}
|
||||
|
@ -3066,7 +3125,8 @@ const wchar_t *reader_readline()
|
|||
|
||||
/* Skip the autosuggestion as history */
|
||||
const wcstring &suggest = data->autosuggestion;
|
||||
if (! suggest.empty()) {
|
||||
if (! suggest.empty())
|
||||
{
|
||||
data->history_search.skip_matches(wcstring_list_t(&suggest, 1 + &suggest));
|
||||
}
|
||||
}
|
||||
|
@ -3083,16 +3143,20 @@ const wchar_t *reader_readline()
|
|||
}
|
||||
else
|
||||
{
|
||||
if (! data->history_search.go_forwards()) {
|
||||
if (! data->history_search.go_forwards())
|
||||
{
|
||||
/* If you try to go forwards past the end, we just go to the end */
|
||||
data->history_search.go_to_end();
|
||||
}
|
||||
}
|
||||
|
||||
wcstring new_text;
|
||||
if (data->history_search.is_at_end()) {
|
||||
if (data->history_search.is_at_end())
|
||||
{
|
||||
new_text = data->search_buff;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
new_text = data->history_search.current_string();
|
||||
}
|
||||
set_command_line_and_position(new_text, new_text.size());
|
||||
|
@ -3138,7 +3202,9 @@ const wchar_t *reader_readline()
|
|||
{
|
||||
data->buff_pos++;
|
||||
reader_repaint();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
accept_autosuggestion();
|
||||
}
|
||||
break;
|
||||
|
@ -3176,7 +3242,8 @@ const wchar_t *reader_readline()
|
|||
{
|
||||
data->history_search = history_search_t(*data->history, data->command_line, HISTORY_SEARCH_TYPE_PREFIX);
|
||||
data->history_search.go_to_beginning();
|
||||
if (! data->history_search.is_at_end()) {
|
||||
if (! data->history_search.is_at_end())
|
||||
{
|
||||
wcstring new_text = data->history_search.current_string();
|
||||
set_command_line_and_position(new_text, new_text.size());
|
||||
}
|
||||
|
|
24
screen.cpp
24
screen.cpp
|
@ -75,7 +75,8 @@ static data_buffer_t *s_writeb_buffer=0;
|
|||
static int s_writeb(char c);
|
||||
|
||||
/* Class to temporarily set s_writeb_buffer and the writer function in a scoped way */
|
||||
class scoped_buffer_t {
|
||||
class scoped_buffer_t
|
||||
{
|
||||
data_buffer_t * const old_buff;
|
||||
int (* const old_writer)(char);
|
||||
|
||||
|
@ -239,9 +240,11 @@ static size_t calc_prompt_width_and_lines( const wchar_t *prompt, size_t *out_pr
|
|||
}
|
||||
|
||||
// PCA for term256 support, let's just detect the escape codes directly
|
||||
if (! found) {
|
||||
if (! found)
|
||||
{
|
||||
len = is_term256_escape(&prompt[j]);
|
||||
if (len) {
|
||||
if (len)
|
||||
{
|
||||
j += (len - 1);
|
||||
found = true;
|
||||
}
|
||||
|
@ -769,7 +772,8 @@ static void s_update( screen_t *scr, const wchar_t *left_prompt, const wchar_t *
|
|||
/* Note that skip_remaining is a width, not a character count */
|
||||
size_t skip_remaining = start_pos;
|
||||
|
||||
if (! should_clear_screen_this_line) {
|
||||
if (! should_clear_screen_this_line)
|
||||
{
|
||||
/* Compute how much we should skip. At a minimum we skip over the prompt. But also skip over the shared prefix of what we want to output now, and what we output before, to avoid repeatedly outputting it. */
|
||||
const size_t shared_prefix = line_shared_prefix(o_line, s_line);
|
||||
if (shared_prefix > 0)
|
||||
|
@ -804,7 +808,8 @@ static void s_update( screen_t *scr, const wchar_t *left_prompt, const wchar_t *
|
|||
}
|
||||
|
||||
/* Maybe clear the screen before outputting. If we clear the screen after outputting, then we may erase the last character due to the sticky right margin. */
|
||||
if (should_clear_screen_this_line) {
|
||||
if (should_clear_screen_this_line)
|
||||
{
|
||||
s_move(scr, &output, current_width, (int)i);
|
||||
s_write_mbs(&output, clr_eos);
|
||||
has_cleared_screen = true;
|
||||
|
@ -890,7 +895,8 @@ static bool is_dumb(void)
|
|||
return (!cursor_up || !cursor_down || !cursor_left || !cursor_right);
|
||||
}
|
||||
|
||||
struct screen_layout_t {
|
||||
struct screen_layout_t
|
||||
{
|
||||
/* The left prompt that we're going to use */
|
||||
wcstring left_prompt;
|
||||
|
||||
|
@ -1310,13 +1316,15 @@ void s_reset( screen_t *s, bool reset_cursor, bool reset_prompt )
|
|||
assert(! reset_cursor || reset_prompt);
|
||||
|
||||
/* If we are resetting the cursor, we're going to make a new line and leave junk behind. If we are not resetting the cursor, we need to remember how many lines we had output to, so we can clear the remaining lines in the next call to s_update. This prevents leaving junk underneath the cursor when resizing a window wider such that it reduces our desired line count. */
|
||||
if (! reset_cursor) {
|
||||
if (! reset_cursor)
|
||||
{
|
||||
s->actual_lines_before_reset = s->actual.line_count();
|
||||
}
|
||||
|
||||
int prev_line = s->actual.cursor.y;
|
||||
|
||||
if (reset_prompt) {
|
||||
if (reset_prompt)
|
||||
{
|
||||
/* If the prompt is multi-line, we need to move up to the prompt's initial line. We do this by lying to ourselves and claiming that we're really below what we consider "line 0" (which is the last line of the prompt). This will cause is to move up to try to get back to line 0, but really we're getting back to the initial line of the prompt. */
|
||||
const size_t prompt_line_count = calc_prompt_lines(s->actual_left_prompt.c_str());
|
||||
assert(prompt_line_count >= 1);
|
||||
|
|
21
screen.h
21
screen.h
|
@ -65,34 +65,41 @@ class screen_data_t
|
|||
|
||||
public:
|
||||
|
||||
struct cursor_t {
|
||||
struct cursor_t
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
cursor_t() : x(0), y(0) { }
|
||||
cursor_t(int a, int b) : x(a), y(b) { }
|
||||
} cursor;
|
||||
|
||||
line_t &add_line(void) {
|
||||
line_t &add_line(void)
|
||||
{
|
||||
line_datas.resize(line_datas.size() + 1);
|
||||
return line_datas.back();
|
||||
}
|
||||
|
||||
void resize(size_t size) {
|
||||
void resize(size_t size)
|
||||
{
|
||||
line_datas.resize(size);
|
||||
}
|
||||
|
||||
line_t &create_line(size_t idx) {
|
||||
if (idx >= line_datas.size()) {
|
||||
line_t &create_line(size_t idx)
|
||||
{
|
||||
if (idx >= line_datas.size())
|
||||
{
|
||||
line_datas.resize(idx + 1);
|
||||
}
|
||||
return line_datas.at(idx);
|
||||
}
|
||||
|
||||
line_t &line(size_t idx) {
|
||||
line_t &line(size_t idx)
|
||||
{
|
||||
return line_datas.at(idx);
|
||||
}
|
||||
|
||||
size_t line_count(void) {
|
||||
size_t line_count(void)
|
||||
{
|
||||
return line_datas.size();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -122,22 +122,28 @@ static void check_locale_init()
|
|||
/* A lot of this code is taken straight from output.cpp; it sure would be nice to factor these together. */
|
||||
|
||||
static bool support_term256;
|
||||
static bool output_get_supports_term256(void) {
|
||||
static bool output_get_supports_term256(void)
|
||||
{
|
||||
return support_term256;
|
||||
}
|
||||
|
||||
static bool term256_support_is_native(void) {
|
||||
static bool term256_support_is_native(void)
|
||||
{
|
||||
/* Return YES if we think the term256 support is "native" as opposed to forced. */
|
||||
return max_colors == 256;
|
||||
}
|
||||
|
||||
static bool write_color(char *todo, unsigned char idx, bool is_fg) {
|
||||
static bool write_color(char *todo, unsigned char idx, bool is_fg)
|
||||
{
|
||||
bool result = false;
|
||||
if (idx < 16 || term256_support_is_native()) {
|
||||
if (idx < 16 || term256_support_is_native())
|
||||
{
|
||||
/* Use tparm */
|
||||
putp(tparm(todo, idx));
|
||||
result = true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We are attempting to bypass the term here. Generate the ANSI escape sequence ourself. */
|
||||
char stridx[128];
|
||||
format_long_safe(stridx, (long)idx);
|
||||
|
@ -151,30 +157,46 @@ static bool write_color(char *todo, unsigned char idx, bool is_fg) {
|
|||
return result;
|
||||
}
|
||||
|
||||
static bool write_foreground_color(unsigned char idx) {
|
||||
if (set_a_foreground && set_a_foreground[0]) {
|
||||
static bool write_foreground_color(unsigned char idx)
|
||||
{
|
||||
if (set_a_foreground && set_a_foreground[0])
|
||||
{
|
||||
return write_color(set_a_foreground, idx, true);
|
||||
} else if (set_foreground && set_foreground[0]) {
|
||||
}
|
||||
else if (set_foreground && set_foreground[0])
|
||||
{
|
||||
return write_color(set_foreground, idx, true);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool write_background_color(unsigned char idx) {
|
||||
if (set_a_background && set_a_background[0]) {
|
||||
static bool write_background_color(unsigned char idx)
|
||||
{
|
||||
if (set_a_background && set_a_background[0])
|
||||
{
|
||||
return write_color(set_a_background, idx, false);
|
||||
} else if (set_background && set_background[0]) {
|
||||
}
|
||||
else if (set_background && set_background[0])
|
||||
{
|
||||
return write_color(set_background, idx, false);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned char index_for_color(rgb_color_t c) {
|
||||
if (c.is_named() || ! output_get_supports_term256()) {
|
||||
static unsigned char index_for_color(rgb_color_t c)
|
||||
{
|
||||
if (c.is_named() || ! output_get_supports_term256())
|
||||
{
|
||||
return c.to_name_index();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return c.to_term256_index();
|
||||
}
|
||||
}
|
||||
|
@ -292,9 +314,12 @@ int main( int argc, char **argv )
|
|||
|
||||
/* Infer term256 support */
|
||||
char *fish_term256 = getenv("fish_term256");
|
||||
if (fish_term256) {
|
||||
if (fish_term256)
|
||||
{
|
||||
support_term256 = from_string<bool>(fish_term256);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *term = getenv("TERM");
|
||||
support_term256 = term && strstr(term, "256color");
|
||||
}
|
||||
|
|
|
@ -127,7 +127,8 @@ void tok_init( tokenizer *tok, const wchar_t *b, int flags )
|
|||
{
|
||||
|
||||
/* We can only generate error messages on the main thread due to wgettext() thread safety issues. */
|
||||
if (! (flags & TOK_SQUASH_ERRORS)) {
|
||||
if (!(flags & TOK_SQUASH_ERRORS))
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
}
|
||||
|
||||
|
|
10
wildcard.cpp
10
wildcard.cpp
|
@ -318,7 +318,8 @@ bool wildcard_match( const wcstring &str, const wcstring &wc )
|
|||
/**
|
||||
Creates a path from the specified directory and filename.
|
||||
*/
|
||||
static wcstring make_path(const wcstring &base_dir, const wcstring &name) {
|
||||
static wcstring make_path(const wcstring &base_dir, const wcstring &name)
|
||||
{
|
||||
return base_dir + name;
|
||||
}
|
||||
|
||||
|
@ -399,9 +400,12 @@ static wcstring complete_get_desc_suffix( const wchar_t *suff_orig )
|
|||
|
||||
std::map<wcstring, wcstring>::iterator iter = suffix_map.find(suff);
|
||||
wcstring desc;
|
||||
if (iter != suffix_map.end()) {
|
||||
if (iter != suffix_map.end())
|
||||
{
|
||||
desc = iter->second;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
desc = complete_get_desc_suffix_internal(suff);
|
||||
}
|
||||
|
||||
|
|
53
wutil.cpp
53
wutil.cpp
|
@ -72,23 +72,32 @@ bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &ou
|
|||
if (!d) return false;
|
||||
|
||||
out_name = str2wcstring(d->d_name);
|
||||
if (out_is_dir) {
|
||||
if (out_is_dir)
|
||||
{
|
||||
/* The caller cares if this is a directory, so check */
|
||||
bool is_dir;
|
||||
if (d->d_type == DT_DIR) {
|
||||
if (d->d_type == DT_DIR)
|
||||
{
|
||||
is_dir = true;
|
||||
} else if (d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) {
|
||||
}
|
||||
else if (d->d_type == DT_LNK || d->d_type == DT_UNKNOWN)
|
||||
{
|
||||
/* We want to treat symlinks to directories as directories. Use stat to resolve it. */
|
||||
cstring fullpath = wcs2string(dir_path);
|
||||
fullpath.push_back('/');
|
||||
fullpath.append(d->d_name);
|
||||
struct stat buf;
|
||||
if (stat(fullpath.c_str(), &buf) != 0) {
|
||||
if (stat(fullpath.c_str(), &buf) != 0)
|
||||
{
|
||||
is_dir = false;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
is_dir = !!(S_ISDIR(buf.st_mode));
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
is_dir = false;
|
||||
}
|
||||
*out_is_dir = is_dir;
|
||||
|
@ -142,7 +151,8 @@ FILE *wfopen(const wcstring &path, const char *mode)
|
|||
{
|
||||
int permissions = 0, options = 0;
|
||||
size_t idx = 0;
|
||||
switch (mode[idx++]) {
|
||||
switch (mode[idx++])
|
||||
{
|
||||
case 'r':
|
||||
permissions = O_RDONLY;
|
||||
break;
|
||||
|
@ -182,13 +192,19 @@ FILE *wfreopen(const wcstring &path, const char *mode, FILE *stream)
|
|||
return freopen(tmp.c_str(), mode, stream);
|
||||
}
|
||||
|
||||
bool set_cloexec(int fd) {
|
||||
bool set_cloexec(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFD, 0);
|
||||
if (flags < 0) {
|
||||
if (flags < 0)
|
||||
{
|
||||
return false;
|
||||
} else if (flags & FD_CLOEXEC) {
|
||||
}
|
||||
else if (flags & FD_CLOEXEC)
|
||||
{
|
||||
return true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return fcntl(fd, F_SETFD, flags | FD_CLOEXEC) >= 0;
|
||||
}
|
||||
}
|
||||
|
@ -199,13 +215,15 @@ static int wopen_internal(const wcstring &pathname, int flags, mode_t mode, bool
|
|||
cstring tmp = wcs2string(pathname);
|
||||
/* Prefer to use O_CLOEXEC. It has to both be defined and nonzero */
|
||||
#ifdef O_CLOEXEC
|
||||
if (cloexec && O_CLOEXEC) {
|
||||
if (cloexec && O_CLOEXEC)
|
||||
{
|
||||
flags |= O_CLOEXEC;
|
||||
cloexec = false;
|
||||
}
|
||||
#endif
|
||||
int fd = ::open(tmp.c_str(), flags, mode);
|
||||
if (cloexec && fd >= 0 && ! set_cloexec(fd)) {
|
||||
if (cloexec && fd >= 0 && ! set_cloexec(fd))
|
||||
{
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
@ -350,7 +368,8 @@ wcstring wbasename( const wcstring &path )
|
|||
}
|
||||
|
||||
/* Really init wgettext */
|
||||
static void wgettext_really_init() {
|
||||
static void wgettext_really_init()
|
||||
{
|
||||
pthread_mutex_init(&wgettext_lock, NULL);
|
||||
bindtextdomain(PACKAGE_NAME, LOCALEDIR);
|
||||
textdomain(PACKAGE_NAME);
|
||||
|
@ -379,7 +398,8 @@ const wchar_t *wgettext( const wchar_t *in )
|
|||
scoped_lock lock(wgettext_lock);
|
||||
|
||||
wcstring *& val = wgettext_map[key];
|
||||
if (val == NULL) {
|
||||
if (val == NULL)
|
||||
{
|
||||
cstring mbs_in = wcs2string(key);
|
||||
char *out = gettext(mbs_in.c_str());
|
||||
val = new wcstring(format_string(L"%s", out));
|
||||
|
@ -388,7 +408,8 @@ const wchar_t *wgettext( const wchar_t *in )
|
|||
return val->c_str();
|
||||
}
|
||||
|
||||
wcstring wgettext2(const wcstring &in) {
|
||||
wcstring wgettext2(const wcstring &in)
|
||||
{
|
||||
wgettext_init_if_necessary();
|
||||
std::string mbs_in = wcs2string(in);
|
||||
char *out = gettext(mbs_in.c_str());
|
||||
|
|
18
xdgmime.cpp
18
xdgmime.cpp
|
@ -123,7 +123,8 @@ xdg_mime_init_from_directory (const char *directory)
|
|||
assert(directory != NULL);
|
||||
|
||||
file_name = (char *)malloc(strlen(directory) + strlen("/mime/globs") + 1);
|
||||
strcpy (file_name, directory); strcat (file_name, "/mime/globs");
|
||||
strcpy(file_name, directory);
|
||||
strcat(file_name, "/mime/globs");
|
||||
if (stat(file_name, &st) == 0)
|
||||
{
|
||||
_xdg_mime_glob_read_from_file(global_hash, file_name);
|
||||
|
@ -140,7 +141,8 @@ xdg_mime_init_from_directory (const char *directory)
|
|||
}
|
||||
|
||||
file_name = (char *)malloc(strlen(directory) + strlen("/mime/magic") + 1);
|
||||
strcpy (file_name, directory); strcat (file_name, "/mime/magic");
|
||||
strcpy(file_name, directory);
|
||||
strcat(file_name, "/mime/magic");
|
||||
if (stat(file_name, &st) == 0)
|
||||
{
|
||||
_xdg_mime_magic_read_from_file(global_magic, file_name);
|
||||
|
@ -157,12 +159,14 @@ xdg_mime_init_from_directory (const char *directory)
|
|||
}
|
||||
|
||||
file_name = (char *)malloc(strlen(directory) + strlen("/mime/aliases") + 1);
|
||||
strcpy (file_name, directory); strcat (file_name, "/mime/aliases");
|
||||
strcpy(file_name, directory);
|
||||
strcat(file_name, "/mime/aliases");
|
||||
_xdg_mime_alias_read_from_file(alias_list, file_name);
|
||||
free(file_name);
|
||||
|
||||
file_name = (char *)malloc(strlen(directory) + strlen("/mime/subclasses") + 1);
|
||||
strcpy (file_name, directory); strcat (file_name, "/mime/subclasses");
|
||||
strcpy(file_name, directory);
|
||||
strcat(file_name, "/mime/subclasses");
|
||||
_xdg_mime_parent_read_from_file(parent_list, file_name);
|
||||
free(file_name);
|
||||
|
||||
|
@ -291,7 +295,8 @@ xdg_check_dir (const char *directory,
|
|||
|
||||
/* Check the globs file */
|
||||
file_name = (char *)malloc(strlen(directory) + strlen("/mime/globs") + 1);
|
||||
strcpy (file_name, directory); strcat (file_name, "/mime/globs");
|
||||
strcpy(file_name, directory);
|
||||
strcat(file_name, "/mime/globs");
|
||||
invalid = xdg_check_file(file_name);
|
||||
free(file_name);
|
||||
if (invalid)
|
||||
|
@ -302,7 +307,8 @@ xdg_check_dir (const char *directory,
|
|||
|
||||
/* Check the magic file */
|
||||
file_name = (char *)malloc(strlen(directory) + strlen("/mime/magic") + 1);
|
||||
strcpy (file_name, directory); strcat (file_name, "/mime/magic");
|
||||
strcpy(file_name, directory);
|
||||
strcat(file_name, "/mime/magic");
|
||||
invalid = xdg_check_file(file_name);
|
||||
free(file_name);
|
||||
if (invalid)
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
#define TRUE (!FALSE)
|
||||
#endif
|
||||
|
||||
static const char _xdg_utf8_skip_data[256] = {
|
||||
static const char _xdg_utf8_skip_data[256] =
|
||||
{
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
|
|
|
@ -639,7 +639,8 @@ _xdg_mime_magic_new (void)
|
|||
void
|
||||
_xdg_mime_magic_free(XdgMimeMagic *mime_magic)
|
||||
{
|
||||
if (mime_magic) {
|
||||
if (mime_magic)
|
||||
{
|
||||
_xdg_mime_magic_match_free(mime_magic->match_list);
|
||||
free(mime_magic);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue