/** Return a description of a file based on its suffix. This function does not perform any caching, it directly calls the mimedb command to do a lookup. */ static wcstring complete_get_desc_suffix_internal( const wcstring &suff ) { wcstring cmd = wcstring(SUFFIX_CMD_STR) + suff; wcstring_list_t lst; wcstring desc; if( exec_subshell( cmd, lst ) != -1 ) { if( lst.size()>0 ) { const wcstring & ln = lst.at(0); if( ln.size() > 0 && ln != L"unknown" ) { desc = ln; /* I have decided I prefer to have the description begin in uppercase and the whole universe will just have to accept it. Hah! */ desc[0]=towupper(desc[0]); } } } if( desc.empty() ) { desc = COMPLETE_FILE_DESC; } suffix_map[suff] = desc.c_str(); return desc; }
void kill_add(const wcstring &str) { ASSERT_IS_MAIN_THREAD(); if (str.empty()) return; wcstring cmd; wcstring escaped_str; kill_list.push_front(str); /* Check to see if user has set the FISH_CLIPBOARD_CMD variable, and, if so, use it instead of checking the display, etc. I couldn't think of a safe way to allow overide of the echo command too, so, the command used must accept the input via stdin. */ const env_var_t clipboard_wstr = env_get_string(L"FISH_CLIPBOARD_CMD"); if (!clipboard_wstr.missing()) { escaped_str = escape(str.c_str(), ESCAPE_ALL); cmd.assign(L"echo -n "); cmd.append(escaped_str); cmd.append(clipboard_wstr); } else { /* This is for sending the kill to the X copy-and-paste buffer */ if (!has_xsel()) { return; } const env_var_t disp_wstr = env_get_string(L"DISPLAY"); if (!disp_wstr.missing()) { escaped_str = escape(str.c_str(), ESCAPE_ALL); cmd.assign(L"echo -n "); cmd.append(escaped_str); cmd.append(L" | xsel -i -b"); } } if (! cmd.empty()) { if (exec_subshell(cmd, false /* do not apply exit status */) == -1) { /* Do nothing on failiure */ } cut_buffer = escaped_str; } }
/** Test if the specified script returns zero. The result is cached, so that if multiple completions use the same condition, it needs only be evaluated once. condition_cache_clear must be called after a completion run to make sure that there are no stale completions. */ static int condition_test( const wchar_t *condition ) { const void *test_res; if( !condition || !wcslen(condition) ) { // fwprintf( stderr, L"No condition specified\n" ); return 1; } if( !condition_cache ) { condition_cache = malloc( sizeof( hash_table_t ) ); if( !condition_cache ) { DIE_MEM(); } hash_init( condition_cache, &hash_wcs_func, &hash_wcs_cmp ); } test_res = hash_get( condition_cache, condition ); if (test_res == CC_NOT_TESTED ) { test_res = exec_subshell( condition, 0 )?CC_FALSE:CC_TRUE; hash_put( condition_cache, condition, test_res ); /* Restore previous status information */ } if( wcscmp( test_res, CC_TRUE ) == 0 ) { return 1; } return 0; }
/// Obtain help/usage information for the specified builtin from manpage in subshell /// /// @param name /// builtin name to get up help for /// /// @return /// A wcstring with a formatted manpage. /// wcstring builtin_help_get(parser_t &parser, io_streams_t &streams, const wchar_t *name) { UNUSED(parser); UNUSED(streams); // This won't ever work if no_exec is set. if (no_exec) return wcstring(); wcstring_list_t lst; wcstring out; const wcstring name_esc = escape_string(name, 1); wcstring cmd = format_string(L"__fish_print_help %ls", name_esc.c_str()); if (exec_subshell(cmd, parser, lst, false /* don't apply exit status */) >= 0) { for (size_t i = 0; i < lst.size(); i++) { out.append(lst.at(i)); out.push_back(L'\n'); } } return out; }
/** Check the X clipboard. If it has been changed, add the new clipboard contents to the fish killring. */ static void kill_check_x_buffer() { if (!has_xsel()) return; const env_var_t disp = env_get_string(L"DISPLAY"); if (! disp.missing()) { size_t i; wcstring cmd = L"xsel -t 500 -b"; wcstring new_cut_buffer=L""; wcstring_list_t list; if (exec_subshell(cmd, list, false /* do not apply exit status */) != -1) { for (i=0; i<list.size(); i++) { wcstring next_line = escape_string(list.at(i), 0); if (i > 0) new_cut_buffer += L"\\n"; new_cut_buffer += next_line; } if (new_cut_buffer.size() > 0) { /* The buffer is inserted with backslash escapes, since we don't really like tabs, newlines, etc. anyway. */ if (cut_buffer != new_cut_buffer) { cut_buffer = new_cut_buffer; kill_list.push_front(new_cut_buffer); } } } } }
/** Perform cmdsubst expansion */ static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<completion_t> &outList) { wchar_t *paran_begin=0, *paran_end=0; std::vector<wcstring> sub_res; size_t i, j; wchar_t *tail_begin = 0; const wchar_t * const in = input.c_str(); int parse_ret; switch (parse_ret = parse_util_locate_cmdsubst(in, ¶n_begin, ¶n_end, 0)) { case -1: parser.error(SYNTAX_ERROR, -1, L"Mismatched parenthesis"); return 0; case 0: outList.push_back(completion_t(input)); return 1; case 1: break; } const wcstring subcmd(paran_begin + 1, paran_end-paran_begin - 1); if (exec_subshell(subcmd, sub_res) == -1) { parser.error(CMDSUBST_ERROR, -1, L"Unknown error while evaulating command substitution"); return 0; } tail_begin = paran_end + 1; if (*tail_begin == L'[') { std::vector<long> slice_idx; wchar_t *slice_end; if (parse_slice(tail_begin, &slice_end, slice_idx, sub_res.size())) { parser.error(SYNTAX_ERROR, -1, L"Invalid index value"); return 0; } else { std::vector<wcstring> sub_res2; tail_begin = slice_end; for (i=0; i < slice_idx.size(); i++) { long idx = slice_idx.at(i); if (idx < 1 || (size_t)idx > sub_res.size()) { parser.error(SYNTAX_ERROR, -1, ARRAY_BOUNDS_ERR); return 0; } idx = idx-1; sub_res2.push_back(sub_res.at(idx)); // debug( 0, L"Pushing item '%ls' with index %d onto sliced result", al_get( sub_res, idx ), idx ); //sub_res[idx] = 0; // ?? } sub_res = sub_res2; } } /* Recursively call ourselves to expand any remaining command substitutions. The result of this recursive call using the tail of the string is inserted into the tail_expand array list */ std::vector<completion_t> tail_expand; expand_cmdsubst(parser, tail_begin, tail_expand); /* Combine the result of the current command substitution with the result of the recursive tail expansion */ for (i=0; i<sub_res.size(); i++) { wcstring sub_item = sub_res.at(i); wcstring sub_item2 = escape_string(sub_item, 1); for (j=0; j < tail_expand.size(); j++) { wcstring whole_item; wcstring tail_item = tail_expand.at(j).completion; //sb_append_substring( &whole_item, in, len1 ); whole_item.append(in, paran_begin-in); //sb_append_char( &whole_item, INTERNAL_SEPARATOR ); whole_item.push_back(INTERNAL_SEPARATOR); //sb_append_substring( &whole_item, sub_item2, item_len ); whole_item.append(sub_item2); //sb_append_char( &whole_item, INTERNAL_SEPARATOR ); whole_item.push_back(INTERNAL_SEPARATOR); //sb_append( &whole_item, tail_item ); whole_item.append(tail_item); //al_push( out, whole_item.buff ); outList.push_back(completion_t(whole_item)); } } return 1; }
/** This internal helper function does all the real work. By using two functions, the internal function can return on various places in the code, and the caller can take care of various cleanup work. cmd: the command name ('grep') really_load: whether to actually parse it as a function, or just check it it exists reload: whether to reload it if it's already loaded path_list: the set of paths to check Result: if really_load is true, returns whether the function was loaded. Otherwise returns whether the function existed. */ bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_load, bool reload, const wcstring_list_t &path_list) { /* Note that we are NOT locked in this function! */ bool reloaded = 0; /* Try using a cached function. If we really want the function to be loaded, require that it be really loaded. If we're not reloading, allow stale functions. */ { bool allow_stale_functions = ! reload; /* Take a lock */ scoped_lock locker(lock); /* Get the function */ autoload_function_t * func = this->get_node(cmd); /* Determine if we can use this cached function */ bool use_cached; if (! func) { /* Can't use a function that doesn't exist */ use_cached = false; } 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)) { /* Can't use a stale function */ use_cached = false; } 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) { return func->is_internalized || func->access.accessible; } } /* The source of the script will end up here */ wcstring script_source; bool has_script_source = false; /* Whether we found an accessible file */ bool found_file = false; /* Look for built-in scripts via a binary search */ const builtin_script_t *matching_builtin_script = NULL; if (builtin_script_count > 0) { const builtin_script_t test_script = {cmd.c_str(), NULL}; const builtin_script_t *array_end = builtin_scripts + builtin_script_count; const builtin_script_t *found = std::lower_bound(builtin_scripts, array_end, test_script, script_name_precedes_script_name); if (found != array_end && ! wcscmp(found->name, test_script.name)) { /* We found it */ matching_builtin_script = found; } } if (matching_builtin_script) { has_script_source = true; 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) { /* Iterate over path searching for suitable completion files */ for (size_t i=0; i<path_list.size(); i++) { wcstring next = path_list.at(i); wcstring path = next + L"/" + cmd + L".fish"; const file_access_attempt_t access = access_file(path, R_OK); if (access.accessible) { /* Found it! */ found_file = true; /* Now we're actually going to take the lock. */ scoped_lock locker(lock); autoload_function_t *func = this->get_node(cmd); /* 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) { /* Generate the script source */ wcstring esc = escape_string(path, 1); script_source = L". " + esc; 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) { command_removed(cmd); func->is_placeholder = false; } /* Mark that we're reloading it */ reloaded = true; } /* 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) { func = get_autoloaded_function_with_creation(cmd, really_load); } /* It's a fiction to say the script is loaded at this point, but we're definitely going to load it down below. */ if (need_to_load_function) func->is_loaded = true; /* Unconditionally record our access time */ func->access = access; break; } } /* If no file or builtin script was found we insert a placeholder function. Later we only research if the current time is at least five seconds later. This way, the files won't be searched over and over again. */ if (! found_file && ! has_script_source) { scoped_lock locker(lock); /* Generate a placeholder */ autoload_function_t *func = this->get_node(cmd); if (! func) { func = new autoload_function_t(cmd); func->is_placeholder = true; if (really_load) { this->add_node(func); } else { this->add_node_without_eviction(func); } } func->access.last_checked = time(NULL); } } /* If we have a script, either built-in or a file source, then run it */ if (really_load && has_script_source) { if (exec_subshell(script_source, false /* do not apply exit status */) == -1) { /* Do nothing on failure */ } } if (really_load) { return reloaded; } else { return found_file || has_script_source; } }
/** If command to complete is short enough, substitute the description with the whatis information for the executable. */ static void complete_cmd_desc( const wchar_t *cmd, array_list_t *comp ) { int i; const wchar_t *cmd_start; int cmd_len; wchar_t *lookup_cmd=0; array_list_t list; hash_table_t lookup; wchar_t *esc; int skip; if( !cmd ) return; cmd_start=wcsrchr(cmd, L'/'); if( cmd_start ) cmd_start++; else cmd_start = cmd; cmd_len = wcslen(cmd_start); /* Using apropos with a single-character search term produces far to many results - require at least two characters if we don't know the location of the whatis-database. */ if(cmd_len < 2 ) return; if( wildcard_has( cmd_start, 0 ) ) { return; } skip = 1; for( i=0; i<al_get_count( comp ); i++ ) { completion_t *c = (completion_t *)al_get( comp, i ); if( !wcslen( c->completion) || (c->completion[wcslen(c->completion)-1] != L'/' )) { skip = 0; break; } } if( skip ) { return; } esc = escape( cmd_start, 1 ); lookup_cmd = wcsdupcat( L"__fish_describe_command ", esc ); free(esc); al_init( &list ); hash_init( &lookup, &hash_wcs_func, &hash_wcs_cmp ); /* First locate a list of possible descriptions using a single call to apropos or a direct search if we know the location of the whatis database. This can take some time on slower systems with a large set of manuals, but it should be ok since apropos is only called once. */ if( exec_subshell( lookup_cmd, &list ) != -1 ) { /* Then discard anything that is not a possible completion and put the result into a hashtable with the completion as key and the description as value. Should be reasonably fast, since no memory allocations are needed. */ for( i=0; i<al_get_count( &list); i++ ) { wchar_t *el = (wchar_t *)al_get( &list, i ); wchar_t *key, *key_end, *val_begin; if( !el ) continue; key = el+wcslen(cmd_start); key_end = wcschr( key, L'\t' ); if( !key_end ) continue; *key_end = 0; val_begin = key_end+1; /* And once again I make sure the first character is uppercased because I like it that way, and I get to decide these things. */ val_begin[0]=towupper(val_begin[0]); hash_put( &lookup, key, val_begin ); } /* Then do a lookup on every completion and if a match is found, change to the new description. This needs to do a reallocation for every description added, but there shouldn't be that many completions, so it should be ok. */ for( i=0; i<al_get_count(comp); i++ ) { completion_t *c = (completion_t *)al_get( comp, i ); const wchar_t *el = c->completion; wchar_t *new_desc; new_desc = (wchar_t *)hash_get( &lookup, el ); if( new_desc ) { c->description = halloc_wcsdup( comp, new_desc ); } } } hash_destroy( &lookup ); al_foreach( &list, &free ); al_destroy( &list ); free( lookup_cmd ); }