mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 21:33:09 +00:00
Some more changes in preparation for turning on complete-based autosuggestions
This commit is contained in:
parent
3553e65089
commit
f74a82776f
10 changed files with 107 additions and 65 deletions
49
autoload.cpp
49
autoload.cpp
|
@ -143,6 +143,34 @@ void autoload_t::unload_all(void) {
|
||||||
this->evict_all_nodes();
|
this->evict_all_nodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Check whether the given command is loaded. */
|
||||||
|
bool autoload_t::has_tried_loading( const wcstring &cmd )
|
||||||
|
{
|
||||||
|
scoped_lock locker(lock);
|
||||||
|
autoload_function_t * func = this->get_node(cmd);
|
||||||
|
return func != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
autoload_function_t *autoload_t::get_autoloaded_function_with_creation(const wcstring &cmd, bool allow_eviction)
|
||||||
|
{
|
||||||
|
ASSERT_IS_LOCKED(lock);
|
||||||
|
autoload_function_t *func = this->get_node(cmd);
|
||||||
|
if (! func) {
|
||||||
|
func = new autoload_function_t(cmd);
|
||||||
|
if (allow_eviction) {
|
||||||
|
this->add_node(func);
|
||||||
|
} else {
|
||||||
|
this->add_node_without_eviction(func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This internal helper function does all the real work. By using two
|
This internal helper function does all the real work. By using two
|
||||||
functions, the internal function can return on various places in
|
functions, the internal function can return on various places in
|
||||||
|
@ -176,7 +204,7 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
||||||
if (! func) {
|
if (! func) {
|
||||||
/* Can't use a function that doesn't exist */
|
/* Can't use a function that doesn't exist */
|
||||||
use_cached = false;
|
use_cached = false;
|
||||||
} else if ( ! allow_stale_functions && time(NULL) - func->access.last_checked > kAutoloadStalenessInterval) {
|
} else if ( ! allow_stale_functions && is_stale(func)) {
|
||||||
/* Can't use a stale function */
|
/* Can't use a stale function */
|
||||||
use_cached = false;
|
use_cached = false;
|
||||||
} else if (really_load && ! func->is_placeholder && ! func->is_loaded) {
|
} else if (really_load && ! func->is_placeholder && ! func->is_loaded) {
|
||||||
|
@ -189,7 +217,7 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
||||||
|
|
||||||
/* If we can use this function, return whether we were able to access it */
|
/* If we can use this function, return whether we were able to access it */
|
||||||
if (use_cached) {
|
if (use_cached) {
|
||||||
return func->access.accessible;
|
return func->is_internalized || func->access.accessible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* The source of the script will end up here */
|
/* The source of the script will end up here */
|
||||||
|
@ -215,6 +243,16 @@ bool autoload_t::locate_file_and_maybe_load_it( const wcstring &cmd, bool really
|
||||||
if (matching_builtin_script) {
|
if (matching_builtin_script) {
|
||||||
has_script_source = true;
|
has_script_source = true;
|
||||||
script_source = str2wcstring(matching_builtin_script->def);
|
script_source = str2wcstring(matching_builtin_script->def);
|
||||||
|
|
||||||
|
/* Make a node representing this function */
|
||||||
|
scoped_lock locker(lock);
|
||||||
|
autoload_function_t *func = this->get_autoloaded_function_with_creation(cmd, really_load);
|
||||||
|
|
||||||
|
/* This function is internalized */
|
||||||
|
func->is_internalized = true;
|
||||||
|
|
||||||
|
/* It's a fiction to say the script is loaded at this point, but we're definitely going to load it down below. */
|
||||||
|
if (really_load) func->is_loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! has_script_source)
|
if (! has_script_source)
|
||||||
|
@ -255,12 +293,7 @@ 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. */
|
/* 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 = new autoload_function_t(cmd);
|
func = get_autoloaded_function_with_creation(cmd, really_load);
|
||||||
if (really_load) {
|
|
||||||
this->add_node(func);
|
|
||||||
} else {
|
|
||||||
this->add_node_without_eviction(func);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* It's a fiction to say the script is loaded at this point, but we're definitely going to load it down below. */
|
/* It's a fiction to say the script is loaded at this point, but we're definitely going to load it down below. */
|
||||||
|
|
|
@ -26,10 +26,11 @@ file_access_attempt_t access_file(const wcstring &path, int mode);
|
||||||
|
|
||||||
struct autoload_function_t : public lru_node_t
|
struct autoload_function_t : public lru_node_t
|
||||||
{
|
{
|
||||||
autoload_function_t(const wcstring &key) : lru_node_t(key), is_loaded(false), is_placeholder(false) { bzero(&access, sizeof access); }
|
autoload_function_t(const wcstring &key) : lru_node_t(key), access(), is_loaded(false), is_placeholder(false), is_internalized(false) { }
|
||||||
file_access_attempt_t access; /** The last access attempt */
|
file_access_attempt_t access; /** The last access attempt */
|
||||||
bool is_loaded; /** Whether we have actually loaded this function */
|
bool is_loaded; /** Whether we have actually loaded this function */
|
||||||
bool is_placeholder; /** Whether we are a placeholder that stands in for "no such function". If this is true, then is_loaded must be false. */
|
bool is_placeholder; /** Whether we are a placeholder that stands in for "no such function". If this is true, then is_loaded must be false. */
|
||||||
|
bool is_internalized; /** Whether this function came from a builtin "internalized" script */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,6 +76,7 @@ private:
|
||||||
|
|
||||||
virtual void node_was_evicted(autoload_function_t *node);
|
virtual void node_was_evicted(autoload_function_t *node);
|
||||||
|
|
||||||
|
autoload_function_t *get_autoloaded_function_with_creation(const wcstring &cmd, bool allow_eviction);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/** Overridable callback for when a command is removed */
|
/** Overridable callback for when a command is removed */
|
||||||
|
@ -101,6 +103,9 @@ private:
|
||||||
*/
|
*/
|
||||||
int load( const wcstring &cmd, bool reload );
|
int load( const wcstring &cmd, bool reload );
|
||||||
|
|
||||||
|
/** Check whether we have tried loading the given command. Does not do any I/O. */
|
||||||
|
bool has_tried_loading( const wcstring &cmd );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Tell the autoloader that the specified file, in the specified path,
|
Tell the autoloader that the specified file, in the specified path,
|
||||||
is no longer loaded.
|
is no longer loaded.
|
||||||
|
|
|
@ -1870,11 +1870,17 @@ void tokenize_variable_array( const wcstring &val, std::vector<wcstring> &out)
|
||||||
out.push_back(val.substr(pos, end - pos));
|
out.push_back(val.substr(pos, end - pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value) {
|
bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value)
|
||||||
|
{
|
||||||
size_t prefix_size = proposed_prefix.size();
|
size_t prefix_size = proposed_prefix.size();
|
||||||
return prefix_size <= value.size() && value.compare(0, prefix_size, proposed_prefix) == 0;
|
return prefix_size <= value.size() && value.compare(0, prefix_size, proposed_prefix) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool list_contains_string(const wcstring_list_t &list, const wcstring &str)
|
||||||
|
{
|
||||||
|
return std::find(list.begin(), list.end(), str) != list.end();
|
||||||
|
}
|
||||||
|
|
||||||
int create_directory( const wcstring &d )
|
int create_directory( const wcstring &d )
|
||||||
{
|
{
|
||||||
int ok = 0;
|
int ok = 0;
|
||||||
|
|
4
common.h
4
common.h
|
@ -250,6 +250,10 @@ std::string wcs2string(const wcstring &input);
|
||||||
/** Test if a string prefixes another. Returns true if a is a prefix of b */
|
/** Test if a string prefixes another. Returns true if a is a prefix of b */
|
||||||
bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value);
|
bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value);
|
||||||
|
|
||||||
|
/** Test if a list contains a string using a linear search. */
|
||||||
|
bool list_contains_string(const wcstring_list_t &list, const wcstring &str);
|
||||||
|
|
||||||
|
|
||||||
void assert_is_main_thread(const char *who);
|
void assert_is_main_thread(const char *who);
|
||||||
#define ASSERT_IS_MAIN_THREAD_TRAMPOLINE(x) assert_is_main_thread(x)
|
#define ASSERT_IS_MAIN_THREAD_TRAMPOLINE(x) assert_is_main_thread(x)
|
||||||
#define ASSERT_IS_MAIN_THREAD() ASSERT_IS_MAIN_THREAD_TRAMPOLINE(__FUNCTION__)
|
#define ASSERT_IS_MAIN_THREAD() ASSERT_IS_MAIN_THREAD_TRAMPOLINE(__FUNCTION__)
|
||||||
|
|
28
complete.cpp
28
complete.cpp
|
@ -227,6 +227,7 @@ class completer_t {
|
||||||
const complete_type_t type;
|
const complete_type_t type;
|
||||||
const wcstring initial_cmd;
|
const wcstring initial_cmd;
|
||||||
std::vector<completion_t> completions;
|
std::vector<completion_t> completions;
|
||||||
|
wcstring_list_t commands_to_load;
|
||||||
|
|
||||||
/** Table of completions conditions that have already been tested and the corresponding test results */
|
/** Table of completions conditions that have already been tested and the corresponding test results */
|
||||||
typedef std::map<wcstring, bool> condition_cache_t;
|
typedef std::map<wcstring, bool> condition_cache_t;
|
||||||
|
@ -235,8 +236,7 @@ class completer_t {
|
||||||
public:
|
public:
|
||||||
completer_t(const wcstring &c, complete_type_t t) :
|
completer_t(const wcstring &c, complete_type_t t) :
|
||||||
type(t),
|
type(t),
|
||||||
initial_cmd(c),
|
initial_cmd(c)
|
||||||
completions()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,6 +269,11 @@ class completer_t {
|
||||||
|
|
||||||
bool condition_test( const wcstring &condition );
|
bool condition_test( const wcstring &condition );
|
||||||
|
|
||||||
|
void get_commands_to_load(wcstring_list_t *lst) {
|
||||||
|
if (lst)
|
||||||
|
lst->insert(lst->end(), commands_to_load.begin(), commands_to_load.end());
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Autoloader for completions */
|
/* Autoloader for completions */
|
||||||
|
@ -609,7 +614,9 @@ int complete_is_valid_option( const wchar_t *str,
|
||||||
/*
|
/*
|
||||||
Make sure completions are loaded for the specified command
|
Make sure completions are loaded for the specified command
|
||||||
*/
|
*/
|
||||||
if (allow_autoload) complete_load( cmd, false );
|
if (allow_autoload) {
|
||||||
|
complete_load( cmd, false );
|
||||||
|
}
|
||||||
|
|
||||||
scoped_lock lock(completion_lock);
|
scoped_lock lock(completion_lock);
|
||||||
scoped_lock lock2(completion_entry_lock);
|
scoped_lock lock2(completion_entry_lock);
|
||||||
|
@ -1245,8 +1252,18 @@ bool completer_t::complete_param( const wcstring &scmd_orig, const wcstring &spo
|
||||||
wcstring cmd, path;
|
wcstring cmd, path;
|
||||||
parse_cmd_string(cmd_orig, path, cmd);
|
parse_cmd_string(cmd_orig, path, cmd);
|
||||||
|
|
||||||
if (type == COMPLETE_DEFAULT)
|
if (this->type == COMPLETE_DEFAULT)
|
||||||
|
{
|
||||||
complete_load( cmd, true );
|
complete_load( cmd, true );
|
||||||
|
}
|
||||||
|
else if (this->type == COMPLETE_AUTOSUGGEST)
|
||||||
|
{
|
||||||
|
/* Maybe indicate we should try loading this on the main thread */
|
||||||
|
if (! list_contains_string(this->commands_to_load, cmd) && ! completion_autoloader.has_tried_loading(cmd))
|
||||||
|
{
|
||||||
|
this->commands_to_load.push_back(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scoped_lock lock(completion_lock);
|
scoped_lock lock(completion_lock);
|
||||||
scoped_lock lock2(completion_entry_lock);
|
scoped_lock lock2(completion_entry_lock);
|
||||||
|
@ -1638,7 +1655,7 @@ bool completer_t::try_complete_user( const wcstring &str )
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void complete( const wcstring &cmd, std::vector<completion_t> &comps, complete_type_t type )
|
void complete( const wcstring &cmd, std::vector<completion_t> &comps, complete_type_t type, wcstring_list_t *commands_to_load )
|
||||||
{
|
{
|
||||||
/* Make our completer */
|
/* Make our completer */
|
||||||
completer_t completer(cmd, type);
|
completer_t completer(cmd, type);
|
||||||
|
@ -1876,6 +1893,7 @@ void complete( const wcstring &cmd, std::vector<completion_t> &comps, complete_t
|
||||||
free( (void *)prev_token );
|
free( (void *)prev_token );
|
||||||
|
|
||||||
comps = completer.get_completions();
|
comps = completer.get_completions();
|
||||||
|
completer.get_commands_to_load(commands_to_load);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -223,8 +223,8 @@ void complete_remove( const wchar_t *cmd,
|
||||||
const wchar_t *long_opt );
|
const wchar_t *long_opt );
|
||||||
|
|
||||||
|
|
||||||
/** Find all completions of the command cmd, insert them into out. */
|
/** Find all completions of the command cmd, insert them into out. If to_load is not NULL, append all commands that we would autoload, but did not (presumably because this is not the main thread) */
|
||||||
void complete( const wcstring &cmd, std::vector<completion_t> &comp, complete_type_t type );
|
void complete( const wcstring &cmd, std::vector<completion_t> &comp, complete_type_t type, wcstring_list_t *to_load = NULL );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Print a list of all current completions into the string_buffer_t.
|
Print a list of all current completions into the string_buffer_t.
|
||||||
|
|
|
@ -550,7 +550,7 @@ bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_di
|
||||||
bool had_cmd = false;
|
bool had_cmd = false;
|
||||||
|
|
||||||
wcstring suggestion;
|
wcstring suggestion;
|
||||||
bool suggestionOK = true;
|
bool suggestionOK = false;
|
||||||
|
|
||||||
tokenizer tok;
|
tokenizer tok;
|
||||||
for( tok_init( &tok, str.c_str(), TOK_SQUASH_ERRORS );
|
for( tok_init( &tok, str.c_str(), TOK_SQUASH_ERRORS );
|
||||||
|
|
24
reader.cpp
24
reader.cpp
|
@ -1225,6 +1225,7 @@ struct autosuggestion_context_t {
|
||||||
file_detection_context_t detector;
|
file_detection_context_t detector;
|
||||||
const wcstring working_directory;
|
const wcstring working_directory;
|
||||||
const env_vars vars;
|
const env_vars vars;
|
||||||
|
wcstring_list_t commands_to_load;
|
||||||
|
|
||||||
autosuggestion_context_t(history_t *history, const wcstring &term) :
|
autosuggestion_context_t(history_t *history, const wcstring &term) :
|
||||||
search_string(term),
|
search_string(term),
|
||||||
|
@ -1261,7 +1262,7 @@ struct autosuggestion_context_t {
|
||||||
|
|
||||||
/* Try normal completions */
|
/* Try normal completions */
|
||||||
std::vector<completion_t> completions;
|
std::vector<completion_t> completions;
|
||||||
//complete2(search_string, completions, COMPLETE_AUTOSUGGEST);
|
//complete(search_string, completions, COMPLETE_AUTOSUGGEST, &this->commands_to_load);
|
||||||
if (! completions.empty()) {
|
if (! completions.empty()) {
|
||||||
this->autosuggestion = this->search_string;
|
this->autosuggestion = this->search_string;
|
||||||
this->autosuggestion.append(completions.at(0).completion);
|
this->autosuggestion.append(completions.at(0).completion);
|
||||||
|
@ -1288,6 +1289,24 @@ static bool can_autosuggest(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
ctx->commands_to_load.swap(commands_to_load);
|
||||||
|
|
||||||
|
/* If we have autosuggestions to load, load them and try again */
|
||||||
|
if (! result && ! commands_to_load.empty())
|
||||||
|
{
|
||||||
|
for (wcstring_list_t::const_iterator iter = commands_to_load.begin(); iter != commands_to_load.end(); iter++)
|
||||||
|
{
|
||||||
|
printf("loading %ls\n", iter->c_str());
|
||||||
|
complete_load(*iter, false);
|
||||||
|
}
|
||||||
|
iothread_perform(threaded_autosuggest, autosuggest_completed, ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (result &&
|
if (result &&
|
||||||
can_autosuggest() &&
|
can_autosuggest() &&
|
||||||
ctx->search_string == data->command_line &&
|
ctx->search_string == data->command_line &&
|
||||||
|
@ -1296,7 +1315,6 @@ static void autosuggest_completed(autosuggestion_context_t *ctx, int result) {
|
||||||
data->autosuggestion = ctx->autosuggestion;
|
data->autosuggestion = ctx->autosuggestion;
|
||||||
sanity_check();
|
sanity_check();
|
||||||
reader_repaint();
|
reader_repaint();
|
||||||
|
|
||||||
}
|
}
|
||||||
delete ctx;
|
delete ctx;
|
||||||
}
|
}
|
||||||
|
@ -2838,7 +2856,7 @@ const wchar_t *reader_readline()
|
||||||
buffcpy = wcsndup( begin, len );
|
buffcpy = wcsndup( begin, len );
|
||||||
|
|
||||||
// comp = al_halloc( 0 );
|
// comp = al_halloc( 0 );
|
||||||
data->complete_func( buffcpy, comp, COMPLETE_DEFAULT );
|
data->complete_func( buffcpy, comp, COMPLETE_DEFAULT, NULL);
|
||||||
|
|
||||||
sort_completion_list( comp );
|
sort_completion_list( comp );
|
||||||
remove_duplicates( comp );
|
remove_duplicates( comp );
|
||||||
|
|
2
reader.h
2
reader.h
|
@ -133,7 +133,7 @@ void reader_pop();
|
||||||
- The command to be completed as a null terminated array of wchar_t
|
- The command to be completed as a null terminated array of wchar_t
|
||||||
- An array_list_t in which completions will be inserted.
|
- An array_list_t in which completions will be inserted.
|
||||||
*/
|
*/
|
||||||
typedef void (*complete_function_t)( const wcstring &, std::vector<completion_t> &, complete_type_t );
|
typedef void (*complete_function_t)( const wcstring &, std::vector<completion_t> &, complete_type_t, wcstring_list_t * lst );
|
||||||
void reader_set_complete_function( complete_function_t );
|
void reader_set_complete_function( complete_function_t );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
42
wutil.cpp
42
wutil.cpp
|
@ -56,24 +56,6 @@ typedef std::string cstring;
|
||||||
*/
|
*/
|
||||||
#define BUFF_COUNT 4
|
#define BUFF_COUNT 4
|
||||||
|
|
||||||
/**
|
|
||||||
For wgettext: The ring of string_buffer_t
|
|
||||||
*/
|
|
||||||
static string_buffer_t buff[BUFF_COUNT];
|
|
||||||
/**
|
|
||||||
For wgettext: Current position in the ring
|
|
||||||
*/
|
|
||||||
static int curr_buff=0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
For wgettext: Buffer used by translate_wcs2str
|
|
||||||
*/
|
|
||||||
static char *wcs2str_buff=0;
|
|
||||||
/**
|
|
||||||
For wgettext: Size of buffer used by translate_wcs2str
|
|
||||||
*/
|
|
||||||
static size_t wcs2str_buff_count=0;
|
|
||||||
|
|
||||||
/* Lock to protect wgettext */
|
/* Lock to protect wgettext */
|
||||||
static pthread_mutex_t wgettext_lock;
|
static pthread_mutex_t wgettext_lock;
|
||||||
|
|
||||||
|
@ -315,10 +297,6 @@ wcstring wbasename( const wcstring &path )
|
||||||
|
|
||||||
/* Really init wgettext */
|
/* Really init wgettext */
|
||||||
static void wgettext_really_init() {
|
static void wgettext_really_init() {
|
||||||
for( size_t i=0; i<BUFF_COUNT; i++ )
|
|
||||||
{
|
|
||||||
sb_init( &buff[i] );
|
|
||||||
}
|
|
||||||
pthread_mutex_init(&wgettext_lock, NULL);
|
pthread_mutex_init(&wgettext_lock, NULL);
|
||||||
bindtextdomain( PACKAGE_NAME, LOCALEDIR );
|
bindtextdomain( PACKAGE_NAME, LOCALEDIR );
|
||||||
textdomain( PACKAGE_NAME );
|
textdomain( PACKAGE_NAME );
|
||||||
|
@ -333,26 +311,6 @@ static void wgettext_init_if_necessary()
|
||||||
pthread_once(&once, wgettext_really_init);
|
pthread_once(&once, wgettext_really_init);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
For wgettext: Wide to narrow character conversion. Internal implementation that
|
|
||||||
avoids exessive calls to malloc
|
|
||||||
*/
|
|
||||||
static char *wgettext_wcs2str( const wchar_t *in )
|
|
||||||
{
|
|
||||||
size_t len = MAX_UTF8_BYTES*wcslen(in)+1;
|
|
||||||
if( len > wcs2str_buff_count )
|
|
||||||
{
|
|
||||||
wcs2str_buff = (char *)realloc( wcs2str_buff, len );
|
|
||||||
if( !wcs2str_buff )
|
|
||||||
{
|
|
||||||
DIE_MEM();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return wcs2str_internal( in, wcs2str_buff);
|
|
||||||
}
|
|
||||||
|
|
||||||
const wchar_t *wgettext( const wchar_t *in )
|
const wchar_t *wgettext( const wchar_t *in )
|
||||||
{
|
{
|
||||||
if( !in )
|
if( !in )
|
||||||
|
|
Loading…
Reference in a new issue