Example #1
0
void write_screen( const wcstring &msg, wcstring &buff )
{
	const wchar_t *start, *pos;
	int line_width = 0;
	int tok_width = 0;
	int screen_width = common_get_width();
	
	if( screen_width )
	{
		start = pos = msg.c_str();
		while( 1 )
		{
			int overflow = 0;
		
			tok_width=0;

			/*
			  Tokenize on whitespace, and also calculate the width of the token
			*/
			while( *pos && ( !wcschr( L" \n\r\t", *pos ) ) )
			{
				
				/*
				  Check is token is wider than one line.
				  If so we mark it as an overflow and break the token.
				*/
				if((tok_width + wcwidth(*pos)) > (screen_width-1))
				{
					overflow = 1;
					break;				
				}
			
				tok_width += wcwidth( *pos );
				pos++;
			}

			/*
			  If token is zero character long, we don't do anything
			*/
			if( pos == start )
			{
				start = pos = pos+1;
			}
			else if( overflow )
			{
				/*
				  In case of overflow, we print a newline, except if we already are at position 0
				*/
				wchar_t *token = wcsndup( start, pos-start );
				if( line_width != 0 )
                    buff.push_back(L'\n');
                buff.append(format_string(L"%ls-\n", token));
				free( token );
				line_width=0;
			}
			else
			{
				/*
				  Print the token
				*/
				wchar_t *token = wcsndup( start, pos-start );
				if( (line_width + (line_width!=0?1:0) + tok_width) > screen_width )
				{
                    buff.push_back(L'\n');
					line_width=0;
				}
                buff.append(format_string(L"%ls%ls", line_width?L" ":L"", token ));
				free( token );
				line_width += (line_width!=0?1:0) + tok_width;
			}
			
			/*
			  Break on end of string
			*/
			if( !*pos )
			{
				break;
			}
			
			start=pos;
		}
	}
	else
	{
        buff.append(msg);
	}
    buff.push_back(L'\n');
}
Example #2
0
/**
   Indent the specified input
 */
static int indent(wcstring &out, const wcstring &in, int flags)
{
    int res=0;
    int is_command = 1;
    int indent = 0;
    int do_indent = 1;
    int prev_type = 0;
    int prev_prev_type = 0;

    tokenizer_t tok(in.c_str(), TOK_SHOW_COMMENTS);
    for (; tok_has_next(&tok); tok_next(&tok))
    {
        int type = tok_last_type(&tok);
        const wchar_t *last = tok_last(&tok);

        switch (type)
        {
            case TOK_STRING:
            {
                if (is_command)
                {
                    int next_indent = indent;
                    is_command = 0;

                    wcstring unesc;
                    unescape_string(last, &unesc, UNESCAPE_SPECIAL);

                    if (parser_keywords_is_block(unesc))
                    {
                        next_indent++;
                    }
                    else if (unesc == L"else")
                    {
                        indent--;
                    }
                    /* case should have the same indent level as switch*/
                    else if (unesc == L"case")
                    {
                        indent--;
                    }
                    else if (unesc == L"end")
                    {
                        indent--;
                        next_indent--;
                    }


                    if (do_indent && flags && prev_type != TOK_PIPE)
                    {
                        insert_tabs(out, indent);
                    }

                    append_format(out, L"%ls", last);

                    indent = next_indent;

                }
                else
                {
                    if (prev_type != TOK_REDIRECT_FD)
                        out.append(L" ");
                    out.append(last);
                }

                break;
            }

            case TOK_END:
            {
                if (prev_type != TOK_END || prev_prev_type != TOK_END)
                    out.append(L"\n");
                do_indent = 1;
                is_command = 1;
                break;
            }

            case TOK_PIPE:
            {
                out.append(L" ");
                if (last[0] == '2' && !last[1])
                {
                    out.append(L"^");
                }
                else if (last[0] != '1' || last[1])
                {
                    out.append(last);
                    out.append(L">");
                }
                out.append(L" | ");
                is_command = 1;
                break;
            }

            case TOK_REDIRECT_OUT:
            {
                out.append(L" ");
                if (wcscmp(last, L"2") == 0)
                {
                    out.append(L"^");
                }
                else
                {
                    if (wcscmp(last, L"1") != 0)
                        out.append(last);
                    out.append(L"> ");
                }
                break;
            }

            case TOK_REDIRECT_APPEND:
            {
                out.append(L" ");
                if (wcscmp(last, L"2") == 0)
                {
                    out.append(L"^^");
                }
                else
                {
                    if (wcscmp(last, L"1") != 0)
                        out.append(last);
                    out.append(L">> ");
                }
                break;
            }

            case TOK_REDIRECT_IN:
            {
                out.append(L" ");
                if (wcscmp(last, L"0") != 0)
                    out.append(last);
                out.append(L"< ");
                break;
            }

            case TOK_REDIRECT_FD:
            {
                out.append(L" ");
                if (wcscmp(last, L"1") != 0)
                    out.append(last);
                out.append(L">& ");
                break;
            }

            case TOK_BACKGROUND:
            {
                out.append(L"&\n");
                do_indent = 1;
                is_command = 1;
                break;
            }

            case TOK_COMMENT:
            {
                if (do_indent && flags)
                {
                    insert_tabs(out, indent);
                }

                append_format(out, L"%ls", last);
                do_indent = 1;
                break;
            }

            default:
            {
                debug(0, L"Unknown token '%ls'", last);
                exit(1);
            }
        }

        prev_prev_type = prev_type;
        prev_type = type;

    }

    return res;
}
Example #3
0
void parser_t::stack_trace(size_t block_idx, wcstring &buff) const
{
    /*
      Check if we should end the recursion
    */
    if (block_idx >= this->block_count())
        return;

    const block_t *b = this->block_at_index(block_idx);

    if (b->type()==EVENT)
    {
        /*
          This is an event handler
        */
        const event_block_t *eb = static_cast<const event_block_t *>(b);
        wcstring description = event_get_desc(eb->event);
        append_format(buff, _(L"in event handler: %ls\n"), description.c_str());
        buff.append(L"\n");

        /*
          Stop recursing at event handler. No reason to believe that
          any other code is relevant.

          It might make sense in the future to continue printing the
          stack trace of the code that invoked the event, if this is a
          programmatic event, but we can't currently detect that.
        */
        return;
    }

    if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW || b->type()==SOURCE || b->type()==SUBST)
    {
        /*
          These types of blocks should be printed
        */

        int i;

        switch (b->type())
        {
            case SOURCE:
            {
                const source_block_t *sb = static_cast<const source_block_t*>(b);
                const wchar_t *source_dest = sb->source_file;
                append_format(buff, _(L"from sourcing file %ls\n"), user_presentable_path(source_dest).c_str());
                break;
            }
            case FUNCTION_CALL:
            case FUNCTION_CALL_NO_SHADOW:
            {
                const function_block_t *fb = static_cast<const function_block_t*>(b);
                append_format(buff, _(L"in function '%ls'\n"), fb->name.c_str());
                break;
            }
            case SUBST:
            {
                append_format(buff, _(L"in command substitution\n"));
                break;
            }

            default: /* Can't get here */
                break;
        }

        const wchar_t *file = b->src_filename;

        if (file)
        {
            append_format(buff,
                          _(L"\tcalled on line %d of file %ls\n"),
                          b->src_lineno,
                          user_presentable_path(file).c_str());
        }
        else if (is_within_fish_initialization)
        {
            append_format(buff, _(L"\tcalled during startup\n"));
        }
        else
        {
            append_format(buff, _(L"\tcalled on standard input\n"));
        }

        if (b->type() == FUNCTION_CALL)
        {
            const function_block_t *fb = static_cast<const function_block_t *>(b);
            const process_t * const process = fb->process;
            if (process->argv(1))
            {
                wcstring tmp;

                for (i=1; process->argv(i); i++)
                {
                    if (i > 1)
                        tmp.push_back(L' ');
                    tmp.append(process->argv(i));
                }
                append_format(buff, _(L"\twith parameter list '%ls'\n"), tmp.c_str());
            }
        }

        append_format(buff, L"\n");
    }

    /*
      Recursively print the next block
    */
    parser_t::stack_trace(block_idx + 1, buff);
}
Example #4
0
/* Cache the data path */
bool path_get_data(wcstring &path)
{
    static const wcstring result = path_create_data();
    path = result;
    return ! result.empty();
}
/// The complete builtin. Used for specifying programmable tab-completions. Calls the functions in
// complete.cpp for any heavy lifting.
int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
    ASSERT_IS_MAIN_THREAD();
    static int recursion_level = 0;

    wchar_t *cmd = argv[0];
    int argc = builtin_count_args(argv);
    int result_mode = SHARED;
    int remove = 0;
    wcstring short_opt;
    wcstring_list_t gnu_opt, old_opt;
    const wchar_t *comp = L"", *desc = L"", *condition = L"";
    bool do_complete = false;
    wcstring do_complete_param;
    wcstring_list_t cmd_to_complete;
    wcstring_list_t path;
    wcstring_list_t wrap_targets;

    const wchar_t *short_options = L":a:c:p:s:l:o:d:frxeuAn:C::w:h";
    const struct woption long_options[] = {{L"exclusive", no_argument, NULL, 'x'},
        {L"no-files", no_argument, NULL, 'f'},
        {L"require-parameter", no_argument, NULL, 'r'},
        {L"path", required_argument, NULL, 'p'},
        {L"command", required_argument, NULL, 'c'},
        {L"short-option", required_argument, NULL, 's'},
        {L"long-option", required_argument, NULL, 'l'},
        {L"old-option", required_argument, NULL, 'o'},
        {L"description", required_argument, NULL, 'd'},
        {L"arguments", required_argument, NULL, 'a'},
        {L"erase", no_argument, NULL, 'e'},
        {L"unauthoritative", no_argument, NULL, 'u'},
        {L"authoritative", no_argument, NULL, 'A'},
        {L"condition", required_argument, NULL, 'n'},
        {L"wraps", required_argument, NULL, 'w'},
        {L"do-complete", optional_argument, NULL, 'C'},
        {L"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };

    int opt;
    wgetopter_t w;
    while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
        switch (opt) {
        case 'x': {
            result_mode |= EXCLUSIVE;
            break;
        }
        case 'f': {
            result_mode |= NO_FILES;
            break;
        }
        case 'r': {
            result_mode |= NO_COMMON;
            break;
        }
        case 'p':
        case 'c': {
            wcstring tmp;
            if (unescape_string(w.woptarg, &tmp, UNESCAPE_SPECIAL)) {
                if (opt == 'p')
                    path.push_back(tmp);
                else
                    cmd_to_complete.push_back(tmp);
            } else {
                streams.err.append_format(_(L"%ls: Invalid token '%ls'\n"), cmd, w.woptarg);
                return STATUS_BUILTIN_ERROR;
            }
            break;
        }
        case 'd': {
            desc = w.woptarg;
            break;
        }
        case 'u': {
            streams.err.append_format(
                _(L"%ls: -u / --unauthoritative flags have been removed\n"), cmd);
            break;
        }
        case 'A': {
            streams.err.append_format(_(L"%ls: -A / --authoritative flags have been removed\n"),
                                      cmd);
            break;
        }
        case 's': {
            short_opt.append(w.woptarg);
            if (w.woptarg[0] == '\0') {
                streams.err.append_format(_(L"%ls: -s requires a non-empty string\n"), cmd);
                return STATUS_BUILTIN_ERROR;
            }
            break;
        }
        case 'l': {
            gnu_opt.push_back(w.woptarg);
            if (w.woptarg[0] == '\0') {
                streams.err.append_format(_(L"%ls: -l requires a non-empty string\n"), cmd);
                return STATUS_BUILTIN_ERROR;
            }
            break;
        }
        case 'o': {
            old_opt.push_back(w.woptarg);
            if (w.woptarg[0] == '\0') {
                streams.err.append_format(_(L"%ls: -o requires a non-empty string\n"), cmd);
                return STATUS_BUILTIN_ERROR;
            }
            break;
        }
        case 'a': {
            comp = w.woptarg;
            break;
        }
        case 'e': {
            remove = 1;
            break;
        }
        case 'n': {
            condition = w.woptarg;
            break;
        }
        case 'w': {
            wrap_targets.push_back(w.woptarg);
            break;
        }
        case 'C': {
            do_complete = true;
            const wchar_t *arg = w.woptarg ? w.woptarg : reader_get_buffer();
            if (arg == NULL) {
                // This corresponds to using 'complete -C' in non-interactive mode.
                // See #2361.
                builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
                return STATUS_BUILTIN_ERROR;
            }
            do_complete_param = arg;
            break;
        }
        case 'h': {
            builtin_print_help(parser, streams, cmd, streams.out);
            return STATUS_BUILTIN_OK;
        }
        case ':': {
            builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
            return STATUS_BUILTIN_ERROR;
        }
        case '?': {
            builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
            return STATUS_BUILTIN_ERROR;
        }
        default: {
            DIE("unexpected retval from wgetopt_long");
            break;
        }
        }
    }

    if (w.woptind != argc) {
        streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd);
        builtin_print_help(parser, streams, cmd, streams.err);
        return STATUS_BUILTIN_ERROR;
    }

    if (condition && wcslen(condition)) {
        const wcstring condition_string = condition;
        parse_error_list_t errors;
        if (parse_util_detect_errors(condition_string, &errors,
                                     false /* do not accept incomplete */)) {
            streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", cmd,
                                      condition);
            for (size_t i = 0; i < errors.size(); i++) {
                streams.err.append_format(L"\n%s: ", cmd);
                streams.err.append(errors.at(i).describe(condition_string));
            }
            return STATUS_BUILTIN_ERROR;
        }
    }

    if (comp && wcslen(comp)) {
        wcstring prefix;
        prefix.append(cmd);
        prefix.append(L": ");

        wcstring err_text;
        if (parser.detect_errors_in_argument_list(comp, &err_text, prefix.c_str())) {
            streams.err.append_format(L"%ls: Completion '%ls' contained a syntax error\n", cmd,
                                      comp);
            streams.err.append(err_text);
            streams.err.push_back(L'\n');
            return STATUS_BUILTIN_ERROR;
        }
    }

    if (do_complete) {
        const wchar_t *token;

        parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, 0,
                                0);

        // Create a scoped transient command line, so that bulitin_commandline will see our
        // argument, not the reader buffer.
        builtin_commandline_scoped_transient_t temp_buffer(do_complete_param);

        if (recursion_level < 1) {
            recursion_level++;

            std::vector<completion_t> comp;
            complete(do_complete_param, &comp, COMPLETION_REQUEST_DEFAULT,
                     env_vars_snapshot_t::current());

            for (size_t i = 0; i < comp.size(); i++) {
                const completion_t &next = comp.at(i);

                // Make a fake commandline, and then apply the completion to it.
                const wcstring faux_cmdline = token;
                size_t tmp_cursor = faux_cmdline.size();
                wcstring faux_cmdline_with_completion = completion_apply_to_command_line(
                        next.completion, next.flags, faux_cmdline, &tmp_cursor, false);

                // completion_apply_to_command_line will append a space unless COMPLETE_NO_SPACE
                // is set. We don't want to set COMPLETE_NO_SPACE because that won't close
                // quotes. What we want is to close the quote, but not append the space. So we
                // just look for the space and clear it.
                if (!(next.flags & COMPLETE_NO_SPACE) &&
                        string_suffixes_string(L" ", faux_cmdline_with_completion)) {
                    faux_cmdline_with_completion.resize(faux_cmdline_with_completion.size() - 1);
                }

                // The input data is meant to be something like you would have on the command
                // line, e.g. includes backslashes. The output should be raw, i.e. unescaped. So
                // we need to unescape the command line. See #1127.
                unescape_string_in_place(&faux_cmdline_with_completion, UNESCAPE_DEFAULT);
                streams.out.append(faux_cmdline_with_completion);

                // Append any description.
                if (!next.description.empty()) {
                    streams.out.push_back(L'\t');
                    streams.out.append(next.description);
                }
                streams.out.push_back(L'\n');
            }

            recursion_level--;
        }
    } else if (cmd_to_complete.empty() && path.empty()) {
        // No arguments specified, meaning we print the definitions of all specified completions
        // to stdout.
        streams.out.append(complete_print());
    } else {
        int flags = COMPLETE_AUTO_SPACE;

        if (remove) {
            builtin_complete_remove(cmd_to_complete, path, short_opt.c_str(), gnu_opt, old_opt);
        } else {
            builtin_complete_add(cmd_to_complete, path, short_opt.c_str(), gnu_opt, old_opt,
                                 result_mode, condition, comp, desc, flags);
        }

        // Handle wrap targets (probably empty). We only wrap commands, not paths.
        for (size_t w = 0; w < wrap_targets.size(); w++) {
            const wcstring &wrap_target = wrap_targets.at(w);
            for (size_t i = 0; i < cmd_to_complete.size(); i++) {
                (remove ? complete_remove_wrapper : complete_add_wrapper)(cmd_to_complete.at(i),
                        wrap_target);
            }
        }
    }

    return STATUS_BUILTIN_OK;
}
Example #6
0
// This function does I/O
static void tokenize( const wchar_t * const buff, std::vector<int> &color, const int pos, wcstring_list_t *error, const wcstring &working_directory, const env_vars_snapshot_t &vars) {
    ASSERT_IS_BACKGROUND_THREAD();
    
	wcstring cmd;    
	int had_cmd=0;
	wcstring last_cmd;
	int len;

	int accept_switches = 1;
	
	int use_function = 1;
	int use_command = 1;
	int use_builtin = 1;
	
	CHECK( buff, );

	len = wcslen(buff);

	if( !len )
		return;

    std::fill(color.begin(), color.end(), -1); 

    tokenizer tok;
	for( tok_init( &tok, buff, TOK_SHOW_COMMENTS | TOK_SQUASH_ERRORS );
		tok_has_next( &tok );
		tok_next( &tok ) )
	{	
		int last_type = tok_last_type( &tok );
		
		switch( last_type )
		{
			case TOK_STRING:
			{
				if( had_cmd )
				{
					
					/*Parameter */
					wchar_t *param = tok_last( &tok );
					if( param[0] == L'-' )
					{
						if (wcscmp( param, L"--" ) == 0 )
						{
							accept_switches = 0;
							color.at(tok_get_pos( &tok )) = HIGHLIGHT_PARAM;
						}
						else if( accept_switches )
						{
							if( complete_is_valid_option( last_cmd.c_str(), param, error, false /* no autoload */ ) )
								color.at(tok_get_pos( &tok )) = HIGHLIGHT_PARAM;
							else
								color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
						}
						else
						{
							color.at(tok_get_pos( &tok )) = HIGHLIGHT_PARAM;
						}
					}
					else
					{
						color.at(tok_get_pos( &tok )) = HIGHLIGHT_PARAM;
					}					

					if( cmd == L"cd" )
					{
                        wcstring dir = tok_last( &tok );
                        if (expand_one(dir, EXPAND_SKIP_CMDSUBST))
						{
							int is_help = string_prefixes_string(dir, L"--help") || string_prefixes_string(dir, L"-h");
							if( !is_help && ! is_potential_cd_path(dir, working_directory, PATH_EXPAND_TILDE, NULL))
							{
                                color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;							
							}
						}
					}
					
                    /* Highlight the parameter. highlight_param wants to write one more color than we have characters (hysterical raisins) so allocate one more in the vector. But don't copy it back. */
                    const wcstring param_str = param;
                    int tok_pos = tok_get_pos(&tok);
                    
                    std::vector<int>::const_iterator where = color.begin() + tok_pos;
                    std::vector<int> subcolors(where, where + param_str.size());
                    subcolors.push_back(-1);                
                    highlight_param(param_str, subcolors, pos-tok_pos, error);
                                        
                    /* Copy the subcolors back into our colors array */
                    std::copy(subcolors.begin(), subcolors.begin() + param_str.size(), color.begin() + tok_pos);
				}
				else
				{ 
					/*
					 Command. First check that the command actually exists.
					 */
                    cmd = tok_last( &tok );
                    bool expanded = expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES);
					if (! expanded || has_expand_reserved(cmd.c_str()))
					{
						color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
					}
					else
					{
						bool is_cmd = false;
						int is_subcommand = 0;
						int mark = tok_get_pos( &tok );
						color.at(tok_get_pos( &tok )) = HIGHLIGHT_COMMAND;
						
						if( parser_keywords_is_subcommand( cmd ) )
						{
							
							int sw;
							
							if( cmd == L"builtin")
							{
								use_function = 0;
								use_command  = 0;
								use_builtin  = 1;
							}
							else if( cmd == L"command")
							{
								use_command  = 1;
								use_function = 0;
								use_builtin  = 0;
							}
							
							tok_next( &tok );
							
							sw = parser_keywords_is_switch( tok_last( &tok ) );
							
							if( !parser_keywords_is_block( cmd ) &&
							   sw == ARG_SWITCH )
							{
								/* 
								 The 'builtin' and 'command' builtins
								 are normally followed by another
								 command, but if they are invoked
								 with a switch, they aren't.
								 
								 */
								use_command  = 1;
								use_function = 1;
								use_builtin  = 2;
							}
							else
							{
								if( sw == ARG_SKIP )
								{
									color.at(tok_get_pos( &tok )) = HIGHLIGHT_PARAM;
									mark = tok_get_pos( &tok );
								}
								
								is_subcommand = 1;
							}
							tok_set_pos( &tok, mark );
						}
						
						if( !is_subcommand )
						{
							/*
							 OK, this is a command, it has been
							 successfully expanded and everything
							 looks ok. Lets check if the command
							 exists.
							 */
							
							/*
							 First check if it is a builtin or
							 function, since we don't have to stat
							 any files for that
							 */
							if (! is_cmd && use_builtin )
								is_cmd = builtin_exists( cmd );
							
							if (! is_cmd && use_function )
								is_cmd = function_exists_no_autoload( cmd, vars );
							
							/*
							 Moving on to expensive tests
							 */
							
							/*
							 Check if this is a regular command
							 */
							if (! is_cmd && use_command )
                            {
								is_cmd = path_get_path( cmd, NULL, vars );
                            }
							
                            /* Maybe it is a path for a implicit cd command. */
                            if (! is_cmd)
                            {
                                if (use_builtin || (use_function && function_exists_no_autoload( L"cd", vars)))
                                    is_cmd = path_can_be_implicit_cd(cmd, NULL, working_directory.c_str(), vars);
                            }
                            
							if( is_cmd )
							{								
								color.at(tok_get_pos( &tok )) = HIGHLIGHT_COMMAND;
							}
							else
							{
								if( error ) {
                                    error->push_back(format_string(L"Unknown command \'%ls\'", cmd.c_str()));
                                }
								color.at(tok_get_pos( &tok )) = (HIGHLIGHT_ERROR);
							}
							had_cmd = 1;
						}
						
						if( had_cmd )
						{
							last_cmd = tok_last( &tok );
						}
					}
					
				}
				break;
			}
				
			case TOK_REDIRECT_NOCLOB:
			case TOK_REDIRECT_OUT:
			case TOK_REDIRECT_IN:
			case TOK_REDIRECT_APPEND:
			case TOK_REDIRECT_FD:
			{
				if( !had_cmd )
				{
					color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
					if( error )
                        error->push_back(L"Redirection without a command");
					break;
				}
				
                wcstring target_str;
				const wchar_t *target=NULL;
				
				color.at(tok_get_pos( &tok )) = HIGHLIGHT_REDIRECTION;
				tok_next( &tok );
				
				/*
				 Check that we are redirecting into a file
				 */
				
				switch( tok_last_type( &tok ) )
				{
					case TOK_STRING:
					{
                        target_str = tok_last( &tok );
                        if (expand_one(target_str, EXPAND_SKIP_CMDSUBST)) {
                            target = target_str.c_str();
                        }
						/*
						 Redirect filename may contain a cmdsubst. 
						 If so, it will be ignored/not flagged.
						 */
					}
						break;
					default:
					{
                        size_t pos = tok_get_pos(&tok);
                        if (pos < color.size()) {
                            color.at(pos) = HIGHLIGHT_ERROR;
                        }
						if( error )
                            error->push_back(L"Invalid redirection");
					}
						
				}
				
				if( target != 0 )
				{
                    wcstring dir = target;
                    size_t slash_idx = dir.find_last_of(L'/');
					struct stat buff;
					/* 
					 If file is in directory other than '.', check
					 that the directory exists.
					 */
					if( slash_idx != wcstring::npos )
					{
						dir.resize(slash_idx);
						if( wstat( dir, &buff ) == -1 )
						{
							color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
							if( error )
                                error->push_back(format_string(L"Directory \'%ls\' does not exist", dir.c_str()));
							
						}
					}
					
					/*
					 If the file is read from or appended to, check
					 if it exists.
					 */
					if( last_type == TOK_REDIRECT_IN || 
					   last_type == TOK_REDIRECT_APPEND )
					{
						if( wstat( target, &buff ) == -1 )
						{
							color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
							if( error )
                                error->push_back(format_string(L"File \'%ls\' does not exist", target));
						}
					}
					if( last_type == TOK_REDIRECT_NOCLOB )
					{
						if( wstat( target, &buff ) != -1 )
						{
							color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
							if( error )
                                error->push_back(format_string(L"File \'%ls\' exists", target));
						}
					}
				}
				break;
			}
				
			case TOK_PIPE:
			case TOK_BACKGROUND:
			{
				if( had_cmd )
				{
					color.at(tok_get_pos( &tok )) = HIGHLIGHT_END;
					had_cmd = 0;
					use_command  = 1;
					use_function = 1;
					use_builtin  = 1;
					accept_switches = 1;
				}
				else
				{
					color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;					
					if( error )
                        error->push_back(L"No job to put in background" );
				}
				
				break;
			}
				
			case TOK_END:
			{
				color.at(tok_get_pos( &tok )) = HIGHLIGHT_END;
				had_cmd = 0;
				use_command  = 1;
				use_function = 1;
				use_builtin  = 1;
				accept_switches = 1;
				break;
			}
				
			case TOK_COMMENT:
			{
				color.at(tok_get_pos( &tok )) = HIGHLIGHT_COMMENT;
				break;
			}
				
			case TOK_ERROR:
			default:
			{
				/*
				 If the tokenizer reports an error, highlight it as such.
				 */
				if( error )
                    error->push_back(tok_last( &tok));
				color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
				break;				
			}			
		}
	}
    tok_destroy( &tok );
}
Example #7
0
bool path_get_cdpath(const wcstring &dir, wcstring *out, const wchar_t *wd, const env_vars_snapshot_t &env_vars)
{
    int err = ENOENT;
    if (dir.empty())
        return false;

    if (wd)
    {
        size_t len = wcslen(wd);
        assert(wd[len - 1] == L'/');
    }

    wcstring_list_t paths;
    if (dir.at(0) == L'/')
    {
        /* Absolute path */
        paths.push_back(dir);
    }
    else if (string_prefixes_string(L"./", dir) ||
             string_prefixes_string(L"../", dir) ||
             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
    {
        // Respect CDPATH
        env_var_t path = env_vars.get(L"CDPATH");
        if (path.missing_or_empty())
            path = L"."; //We'll change this to the wd if we have one

        wcstring nxt_path;
        wcstokenizer tokenizer(path, ARRAY_SEP_STR);
        while (tokenizer.next(nxt_path))
        {

            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;
            }
            expand_tilde(nxt_path);

//      debug( 2, L"woot %ls\n", expanded_path.c_str() );

            if (nxt_path.empty())
                continue;

            wcstring whole_path = nxt_path;
            append_path_component(whole_path, dir);
            paths.push_back(whole_path);
        }
    }

    bool success = false;
    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)
        {
            if (S_ISDIR(buf.st_mode))
            {
                success = true;
                if (out)
                    out->assign(dir);
                break;
            }
            else
            {
                err = ENOTDIR;
            }
        }
    }

    if (! success)
        errno = err;
    return success;
}
Example #8
0
static bool is_electric(const wcstring &key)
{
    return env_electric.find(key.c_str()) != env_electric.end();
}
Example #9
0
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[] =
    {
        L"status",
        L"history",
        L"version",
        L"_",
        L"LINES",
        L"COLUMNS",
        L"PWD",
        //L"SHLVL", // will be inserted a bit lower down
        L"FISH_VERSION",
    };
    for (size_t i=0; i < sizeof ro_keys / sizeof *ro_keys; i++)
    {
        env_read_only.insert(ro_keys[i]);
    }

    /*
       Names of all dynamically calculated variables
       */
    env_electric.insert(L"history");
    env_electric.insert(L"status");
    env_electric.insert(L"umask");
    env_electric.insert(L"COLUMNS");
    env_electric.insert(L"LINES");

    top = new env_node_t;
    global_env = top;
    global = &top->env;

    /*
      Now the environemnt variable handling is set up, the next step
      is to insert valid data
    */

    /*
      Import environment variables
    */
    for (char **p = (environ ? environ : __environ); p && *p; p++)
    {
        const wcstring key_and_val = str2wcstring(*p); //like foo=bar
        size_t eql = key_and_val.find(L'=');
        if (eql == wcstring::npos)
        {
            // no equals found
            if (is_read_only(key_and_val) || is_electric(key_and_val)) continue;
            env_set(key_and_val, L"", ENV_EXPORT | ENV_GLOBAL);
        }
        else
        {
            wcstring key = key_and_val.substr(0, eql);
            if (is_read_only(key) || is_electric(key)) continue;
            wcstring val = key_and_val.substr(eql + 1);
            if (variable_is_colon_delimited_array(key))
            {
                std::replace(val.begin(), val.end(), L':', ARRAY_SEP);
            }

            env_set(key, val.c_str(), ENV_EXPORT | ENV_GLOBAL);
        }
    }

    /* Set the given paths in the environment, if we have any */
    if (paths != NULL)
    {
        env_set(FISH_DATADIR_VAR, paths->data.c_str(), ENV_GLOBAL);
        env_set(FISH_SYSCONFDIR_VAR, paths->sysconf.c_str(), ENV_GLOBAL);
        env_set(FISH_HELPDIR_VAR, paths->doc.c_str(), ENV_GLOBAL);
        env_set(FISH_BIN_DIR, paths->bin.c_str(), ENV_GLOBAL);
    }

    /*
      Set up the PATH variable
    */
    setup_path();

    /*
      Set up the USER variable
    */
    if (env_get_string(L"USER").missing_or_empty())
    {
        const struct passwd *pw = getpwuid(getuid());
        if (pw && pw->pw_name)
        {
            const wcstring uname = str2wcstring(pw->pw_name);
            env_set(L"USER", uname.c_str(), ENV_GLOBAL | ENV_EXPORT);
        }
    }

    /*
      Set up the version variables
    */
    wcstring version = str2wcstring(get_fish_version());
    env_set(L"version", version.c_str(), ENV_GLOBAL);
    env_set(L"FISH_VERSION", version.c_str(), ENV_GLOBAL);

    /*
      Set up SHLVL variable
    */
    const env_var_t shlvl_str = env_get_string(L"SHLVL");
    wcstring nshlvl_str = L"1";
    if (! shlvl_str.missing())
    {
        wchar_t *end;
        long shlvl_i = wcstol(shlvl_str.c_str(), &end, 10);
        while (iswspace(*end)) ++end; /* skip trailing whitespace */
        if (shlvl_i >= 0 && *end == '\0')
        {
            nshlvl_str = to_string<long>(shlvl_i + 1);
        }
    }
    env_set(L"SHLVL", nshlvl_str.c_str(), ENV_GLOBAL | ENV_EXPORT);
    env_read_only.insert(L"SHLVL");

    /* Set up the HOME variable */
    if (env_get_string(L"HOME").missing_or_empty())
    {
        const env_var_t unam = env_get_string(L"USER");
        char *unam_narrow = wcs2str(unam.c_str());
        struct passwd *pw = getpwnam(unam_narrow);
        if (pw->pw_dir != NULL)
        {
            const wcstring dir = str2wcstring(pw->pw_dir);
            env_set(L"HOME", dir.c_str(), ENV_GLOBAL | ENV_EXPORT);
        }
        free(unam_narrow);
    }

    /* Set PWD */
    env_set_pwd();

    /* Set up universal variables. The empty string means to use the deafult path. */
    assert(s_universal_variables == NULL);
    s_universal_variables = new env_universal_t(L"");
    s_universal_variables->load();

    /* Set g_log_forks */
    env_var_t log_forks = env_get_string(L"fish_log_forks");
    g_log_forks = ! log_forks.missing_or_empty() && from_string<bool>(log_forks);

    /* Set g_use_posix_spawn. Default to true. */
    env_var_t use_posix_spawn = env_get_string(L"fish_use_posix_spawn");
    g_use_posix_spawn = (use_posix_spawn.missing_or_empty() ? true : from_string<bool>(use_posix_spawn));

    /* Set fish_bind_mode to "default" */
    env_set(FISH_BIND_MODE_VAR, DEFAULT_BIND_MODE, ENV_GLOBAL);

    /*
      Now that the global scope is fully initialized, add a toplevel local
      scope. This same local scope will persist throughout the lifetime of the
      fish process, and it will ensure that `set -l` commands run at the
      command-line don't affect the global scope.
    */
    env_push(false);
}
Example #10
0
int job_reap(bool interactive)
{
    ASSERT_IS_MAIN_THREAD();
    job_t *jnext;
    int found=0;

    /* job_reap may fire an event handler, we do not want to call ourselves recursively (to avoid infinite recursion). */
    static bool locked = false;
    if (locked)
    {
        return 0;
    }
    locked = true;
    
    process_mark_finished_children(false);

    /* Preserve the exit status */
    const int saved_status = proc_get_last_status();

    job_iterator_t jobs;
    const size_t job_count = jobs.count();
    jnext = jobs.next();
    while (jnext)
    {
        job_t *j = jnext;
        jnext = jobs.next();

        /*
          If we are reaping only jobs who do not need status messages
          sent to the console, do not consider reaping jobs that need
          status messages
        */
        if ((!job_get_flag(j, JOB_SKIP_NOTIFICATION)) && (!interactive) && (!job_get_flag(j, JOB_FOREGROUND)))
        {
            continue;
        }

        for (process_t *p = j->first_process; p; p=p->next)
        {
            int s;
            if (!p->completed)
                continue;

            if (!p->pid)
                continue;

            s = p->status;

            proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, p->pid, (WIFSIGNALED(s)?-1:WEXITSTATUS(s)));

            if (WIFSIGNALED(s))
            {
                /*
                   Ignore signal SIGPIPE.We issue it ourselves to the pipe
                   writer when the pipe reader dies.
                */
                if (WTERMSIG(s) != SIGPIPE)
                {
                    int proc_is_job = ((p==j->first_process) && (p->next == 0));
                    if (proc_is_job)
                        job_set_flag(j, JOB_NOTIFIED, 1);
                    if (!job_get_flag(j, JOB_SKIP_NOTIFICATION))
                    {
                        /* Print nothing if we get SIGINT in the foreground process group, to avoid spamming obvious stuff on the console (#1119). If we get SIGINT for the foreground process, assume the user typed ^C and can see it working. It's possible they didn't, and the signal was delivered via pkill, etc., but the SIGINT/SIGTERM distinction is precisely to allow INT to be from a UI and TERM to be programmatic, so this assumption is keeping with the design of signals.
                        If echoctl is on, then the terminal will have written ^C to the console. If off, it won't have. We don't echo ^C either way, so as to respect the user's preference. */
                        if (WTERMSIG(p->status) != SIGINT || ! job_get_flag(j, JOB_FOREGROUND))
                            {
                            if (proc_is_job)
                            {
                                // We want to report the job number, unless it's the only job, in which case we don't need to
                                const wcstring job_number_desc = (job_count == 1) ? wcstring() : format_string(L"Job %d, ", j->job_id);
                                fwprintf(stdout,
                                         _(L"%ls: %ls\'%ls\' terminated by signal %ls (%ls)"),
                                         program_name,
                                         job_number_desc.c_str(),
                                         truncate_command(j->command()).c_str(),
                                         sig2wcs(WTERMSIG(p->status)),
                                         signal_get_desc(WTERMSIG(p->status)));
                            }
                            else
                            {
                                const wcstring job_number_desc = (job_count == 1) ? wcstring() : format_string(L"from job %d, ", j->job_id);
                                fwprintf(stdout,
                                         _(L"%ls: Process %d, \'%ls\' %ls\'%ls\' terminated by signal %ls (%ls)"),
                                         program_name,
                                         p->pid,
                                         p->argv0(),
                                         job_number_desc.c_str(),
                                         truncate_command(j->command()).c_str(),
                                         sig2wcs(WTERMSIG(p->status)),
                                         signal_get_desc(WTERMSIG(p->status)));
                            }
                            tputs(clr_eol,1,&writeb);
                            fwprintf(stdout, L"\n");
                        }
                        found=1;
                    }

                    /*
                       Clear status so it is not reported more than once
                    */
                    p->status = 0;
                }
            }
        }

        /*
           If all processes have completed, tell the user the job has
           completed and delete it from the active job list.
        */
        if (job_is_completed(j))
        {
            if (!job_get_flag(j, JOB_FOREGROUND) && !job_get_flag(j, JOB_NOTIFIED) && !job_get_flag(j, JOB_SKIP_NOTIFICATION))
            {
                format_job_info(j, _(L"ended"), job_count);
                found=1;
            }
            proc_fire_event(L"JOB_EXIT", EVENT_EXIT, -j->pgid, 0);
            proc_fire_event(L"JOB_EXIT", EVENT_JOB_ID, j->job_id, 0);

            job_free(j);
        }
        else if (job_is_stopped(j) && !job_get_flag(j, JOB_NOTIFIED))
        {
            /*
               Notify the user about newly stopped jobs.
            */
            if (!job_get_flag(j, JOB_SKIP_NOTIFICATION))
            {
                format_job_info(j, _(L"stopped"), job_count);
                found=1;
            }
            job_set_flag(j, JOB_NOTIFIED, 1);
        }
    }

    if (found)
        fflush(stdout);

    /* Restore the exit status. */
    proc_set_last_status(saved_status);

    locked = false;

    return found;
}
Example #11
0
static bool is_read_only(const wcstring &key)
{
    return env_read_only.find(key.c_str()) != env_read_only.end();
}
Example #12
0
static screen_layout_t compute_layout(screen_t *s, size_t screen_width,
                                      const wcstring &left_prompt_str,
                                      const wcstring &right_prompt_str, const wcstring &commandline,
                                      const wcstring &autosuggestion_str, const int *indent) {
    screen_layout_t result = {};

    // Start by ensuring that the prompts themselves can fit.
    const wchar_t *left_prompt = left_prompt_str.c_str();
    const wchar_t *right_prompt = right_prompt_str.c_str();
    const wchar_t *autosuggestion = autosuggestion_str.c_str();

    prompt_layout_t left_prompt_layout = calc_prompt_layout(left_prompt);
    prompt_layout_t right_prompt_layout = calc_prompt_layout(right_prompt);

    size_t left_prompt_width = left_prompt_layout.last_line_width;
    size_t right_prompt_width = right_prompt_layout.last_line_width;

    if (left_prompt_layout.max_line_width > screen_width) {
        // If we have a multi-line prompt, see if the longest line fits; if not neuter the whole
        // left prompt.
        left_prompt = L"> ";
        left_prompt_width = 2;
    }

    if (left_prompt_width + right_prompt_width >= screen_width) {
        // Nix right_prompt.
        right_prompt = L"";
        right_prompt_width = 0;
    }

    if (left_prompt_width + right_prompt_width >= screen_width) {
        // Still doesn't fit, neuter left_prompt.
        left_prompt = L"> ";
        left_prompt_width = 2;
    }

    // Now we should definitely fit.
    assert(left_prompt_width + right_prompt_width < screen_width);

    // Convert commandline to a list of lines and their widths.
    wcstring_list_t command_lines(1);
    std::vector<size_t> line_widths(1);
    for (size_t i = 0; i < commandline.size(); i++) {
        wchar_t c = commandline.at(i);
        if (c == L'\n') {
            // Make a new line.
            command_lines.push_back(wcstring());
            line_widths.push_back(indent[i] * INDENT_STEP);
        } else {
            command_lines.back() += c;
            line_widths.back() += fish_wcwidth_min_0(c);
        }
    }
    const size_t first_command_line_width = line_widths.at(0);

    // If we have more than one line, ensure we have no autosuggestion.
    if (command_lines.size() > 1) {
        autosuggestion = L"";
    }

    // Compute the width of the autosuggestion at all possible truncation offsets.
    std::vector<size_t> autosuggest_truncated_widths;
    autosuggest_truncated_widths.reserve(1 + wcslen(autosuggestion));
    size_t autosuggest_total_width = 0;
    for (size_t i = 0; autosuggestion[i] != L'\0'; i++) {
        autosuggest_truncated_widths.push_back(autosuggest_total_width);
        autosuggest_total_width += fish_wcwidth_min_0(autosuggestion[i]);
    }

    // Here are the layouts we try in turn:
    //
    // 1. Left prompt visible, right prompt visible, command line visible, autosuggestion visible.
    //
    // 2. Left prompt visible, right prompt visible, command line visible, autosuggestion truncated
    // (possibly to zero).
    //
    // 3. Left prompt visible, right prompt hidden, command line visible, autosuggestion hidden.
    //
    // 4. Newline separator (left prompt visible, right prompt hidden, command line visible,
    // autosuggestion visible).
    //
    // A remark about layout #4: if we've pushed the command line to a new line, why can't we draw
    // the right prompt? The issue is resizing: if you resize the window smaller, then the right
    // prompt will wrap to the next line. This means that we can't go back to the line that we were
    // on, and things turn to chaos very quickly.
    bool done = false;

    // Case 1
    if (!done &&
        left_prompt_width + right_prompt_width + first_command_line_width +
                autosuggest_total_width <
            screen_width) {
        result.left_prompt = left_prompt;
        result.left_prompt_space = left_prompt_width;
        result.right_prompt = right_prompt;
        result.autosuggestion = autosuggestion;
        done = true;
    }

    // Case 2. Note that we require strict inequality so that there's always at least one space
    // between the left edge and the rprompt.
    if (!done && left_prompt_width + right_prompt_width + first_command_line_width < screen_width) {
        result.left_prompt = left_prompt;
        result.left_prompt_space = left_prompt_width;
        result.right_prompt = right_prompt;

        // Need at least two characters to show an autosuggestion.
        size_t available_autosuggest_space =
            screen_width - (left_prompt_width + right_prompt_width + first_command_line_width);
        if (autosuggest_total_width > 0 && available_autosuggest_space > 2) {
            size_t truncation_offset = truncation_offset_for_width(autosuggest_truncated_widths,
                                                                   available_autosuggest_space - 2);
            result.autosuggestion = wcstring(autosuggestion, truncation_offset);
            result.autosuggestion.push_back(ellipsis_char);
        }
        done = true;
    }

    // Case 3
    if (!done && left_prompt_width + first_command_line_width < screen_width) {
        result.left_prompt = left_prompt;
        result.left_prompt_space = left_prompt_width;
        done = true;
    }

    // Case 4
    if (!done) {
        result.left_prompt = left_prompt;
        result.left_prompt_space = left_prompt_width;
        // See remark about for why we can't use the right prompt here result.right_prompt =
        // right_prompt. If the command wraps, and the prompt is not short, place the command on its
        // own line. A short prompt is 33% or less of the terminal's width.
        const size_t prompt_percent_width = (100 * left_prompt_width) / screen_width;
        if (left_prompt_width + first_command_line_width + 1 > screen_width &&
            prompt_percent_width > 33) {
            result.prompts_get_own_line = true;
        }

        done = true;
    }

    assert(done);
    return result;
}
Example #13
0
void s_write(screen_t *s, const wcstring &left_prompt, const wcstring &right_prompt,
             const wcstring &commandline, size_t explicit_len, const highlight_spec_t *colors,
             const int *indent, size_t cursor_pos, const page_rendering_t &pager,
             bool cursor_is_within_pager) {
    screen_data_t::cursor_t cursor_arr;

    CHECK(s, );
    CHECK(indent, );

    // Turn the command line into the explicit portion and the autosuggestion.
    const wcstring explicit_command_line = commandline.substr(0, explicit_len);
    const wcstring autosuggestion = commandline.substr(explicit_len);

    // If we are using a dumb terminal, don't try any fancy stuff, just print out the text.
    // right_prompt not supported.
    if (is_dumb()) {
        const std::string prompt_narrow = wcs2string(left_prompt);
        const std::string command_line_narrow = wcs2string(explicit_command_line);

        write_loop(STDOUT_FILENO, "\r", 1);
        write_loop(STDOUT_FILENO, prompt_narrow.c_str(), prompt_narrow.size());
        write_loop(STDOUT_FILENO, command_line_narrow.c_str(), command_line_narrow.size());

        return;
    }

    s_check_status(s);
    const size_t screen_width = common_get_width();

    // Completely ignore impossibly small screens.
    if (screen_width < 4) {
        return;
    }

    // Compute a layout.
    const screen_layout_t layout = compute_layout(s, screen_width, left_prompt, right_prompt,
                                                  explicit_command_line, autosuggestion, indent);

    // Determine whether, if we have an autosuggestion, it was truncated.
    s->autosuggestion_is_truncated =
        !autosuggestion.empty() && autosuggestion != layout.autosuggestion;

    // Clear the desired screen.
    s->desired.resize(0);
    s->desired.cursor.x = s->desired.cursor.y = 0;

    // Append spaces for the left prompt.
    for (size_t i = 0; i < layout.left_prompt_space; i++) {
        s_desired_append_char(s, L' ', 0, 0, layout.left_prompt_space);
    }

    // If overflowing, give the prompt its own line to improve the situation.
    size_t first_line_prompt_space = layout.left_prompt_space;
    if (layout.prompts_get_own_line) {
        s_desired_append_char(s, L'\n', 0, 0, 0);
        first_line_prompt_space = 0;
    }

    // Reconstruct the command line.
    wcstring effective_commandline = explicit_command_line + layout.autosuggestion;

    // Output the command line.
    size_t i;
    for (i = 0; i < effective_commandline.size(); i++) {
        // Grab the current cursor's x,y position if this character matches the cursor's offset.
        if (!cursor_is_within_pager && i == cursor_pos) {
            cursor_arr = s->desired.cursor;
        }
        s_desired_append_char(s, effective_commandline.at(i), colors[i], indent[i],
                              first_line_prompt_space);
    }

    // Cursor may have been at the end too.
    if (!cursor_is_within_pager && i == cursor_pos) {
        cursor_arr = s->desired.cursor;
    }

    // Now that we've output everything, set the cursor to the position that we saved in the loop
    // above.
    s->desired.cursor = cursor_arr;

    if (cursor_is_within_pager) {
        s->desired.cursor.x = (int)cursor_pos;
        s->desired.cursor.y = (int)s->desired.line_count();
    }

    // Append pager_data (none if empty).
    s->desired.append_lines(pager.screen_data);

    s_update(s, layout.left_prompt.c_str(), layout.right_prompt.c_str());
    s_save_status(s);
}
Example #14
0
int main(int argc, char **argv) {
    int res = 1;
    int my_optind = 0;

    program_name = L"fish";
    set_main_thread();
    setup_fork_guards();

    setlocale(LC_ALL, "");
    fish_setlocale();

    // struct stat tmp;
    // stat("----------FISH_HIT_MAIN----------", &tmp);

    if (!argv[0]) {
        static const char *dummy_argv[2] = {"fish", NULL};
        argv = (char **)dummy_argv;  //!OCLINT(parameter reassignment)
        argc = 1;                    //!OCLINT(parameter reassignment)
    }
    std::vector<std::string> cmds;
    my_optind = fish_parse_opt(argc, argv, &cmds);

    // No-exec is prohibited when in interactive mode.
    if (is_interactive_session && no_exec) {
        debug(1, _(L"Can not use the no-execute mode when running an interactive session"));
        no_exec = 0;
    }

    // Only save (and therefore restore) the fg process group if we are interactive. See issues
    // #197 and #1002.
    if (is_interactive_session) {
        save_term_foreground_process_group();
    }

    const struct config_paths_t paths = determine_config_directory_paths(argv[0]);

    proc_init();
    event_init();
    builtin_init();
    function_init();
    env_init(&paths);
    reader_init();
    history_init();
    // For set_color to support term256 in config.fish (issue #1022).
    update_fish_color_support();
    misc_init();

    parser_t &parser = parser_t::principal_parser();

    const io_chain_t empty_ios;
    if (read_init(paths)) {
        // Stomp the exit status of any initialization commands (issue #635).
        proc_set_last_status(STATUS_BUILTIN_OK);

        // Run the commands specified as arguments, if any.
        if (!cmds.empty()) {
            // Do something nasty to support OpenSUSE assuming we're bash. This may modify cmds.
            if (is_login) {
                fish_xdm_login_hack_hack_hack_hack(&cmds, argc - my_optind, argv + my_optind);
            }
            for (size_t i = 0; i < cmds.size(); i++) {
                const wcstring cmd_wcs = str2wcstring(cmds.at(i));
                res = parser.eval(cmd_wcs, empty_ios, TOP);
            }
            reader_exit(0, 0);
        } else if (my_optind == argc) {
            // Interactive mode
            check_running_fishd();
            res = reader_read(STDIN_FILENO, empty_ios);
        } else {
            char *file = *(argv + (my_optind++));
            int fd = open(file, O_RDONLY);
            if (fd == -1) {
                perror(file);
            } else {
                // OK to not do this atomically since we cannot have gone multithreaded yet.
                set_cloexec(fd);

                if (*(argv + my_optind)) {
                    wcstring sb;
                    char **ptr;
                    int i;
                    for (i = 1, ptr = argv + my_optind; *ptr; i++, ptr++) {
                        if (i != 1) sb.append(ARRAY_SEP_STR);
                        sb.append(str2wcstring(*ptr));
                    }

                    env_set(L"argv", sb.c_str(), 0);
                }

                const wcstring rel_filename = str2wcstring(file);

                reader_push_current_filename(rel_filename.c_str());

                res = reader_read(fd, empty_ios);

                if (res) {
                    debug(1, _(L"Error while reading file %ls\n"), reader_current_filename()
                                                                       ? reader_current_filename()
                                                                       : _(L"Standard input"));
                }
                reader_pop_current_filename();
            }
        }
    }

    int exit_status = res ? STATUS_UNKNOWN_COMMAND : proc_get_last_status();

    proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, getpid(), exit_status);

    restore_term_mode();
    restore_term_foreground_process_group();

    if (g_profiling_active) {
        parser.emit_profiling(s_profiling_output_filename);
    }

    history_destroy();
    proc_destroy();
    builtin_destroy();
    reader_destroy();
    event_destroy();
    exit_without_destructors(exit_status);
    return EXIT_FAILURE;  // above line should always exit
}
Example #15
0
/* Parse a command line. Return by reference the last command, its arguments, and the offset in the string of the beginning of the last argument. This is used by autosuggestions */
static bool autosuggest_parse_command(const wcstring &str, wcstring *out_command, wcstring_list_t *out_arguments, int *out_last_arg_pos)
{
    if (str.empty())
        return false;
    
    wcstring cmd;
    wcstring_list_t args;
    int arg_pos = -1;
    
    bool had_cmd = false;
    tokenizer tok;
    for (tok_init( &tok, str.c_str(), TOK_ACCEPT_UNFINISHED | TOK_SQUASH_ERRORS); tok_has_next(&tok); tok_next(&tok))
    {
        int last_type = tok_last_type(&tok);
        
        switch( last_type )
        {
            case TOK_STRING:
            {
                if( had_cmd )
                {
                    /* Parameter to the command. We store these escaped. */
                    args.push_back(tok_last(&tok));
                    arg_pos = tok_get_pos(&tok);
                }
                else
                { 	
                    /* Command. First check that the command actually exists. */
                    wcstring local_cmd = tok_last( &tok );
                    bool expanded = expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES);
                    if (! expanded || has_expand_reserved(cmd.c_str()))
                    {
                        /* We can't expand this cmd, ignore it */
                    }
                    else
                    {
                        bool is_subcommand = false;
                        int mark = tok_get_pos(&tok);
                        
                        if (parser_keywords_is_subcommand(cmd))
                        {
                            int sw;
                            tok_next( &tok );
                            
                            sw = parser_keywords_is_switch( tok_last( &tok ) );
                            if( !parser_keywords_is_block( cmd ) &&
                               sw == ARG_SWITCH )
                            {
                                /* It's an argument to the subcommand itself */
                            }
                            else
                            {
                                if( sw == ARG_SKIP )
                                    mark = tok_get_pos( &tok );                                    
                                is_subcommand = true;
                            }
                            tok_set_pos( &tok, mark );
                        }
                        
                        if (!is_subcommand)
                        {
                            /* It's really a command */
                            had_cmd = true;
                            cmd = local_cmd;
                        }
                    }
                    
                }
                break;
            }
                
            case TOK_REDIRECT_NOCLOB:
            case TOK_REDIRECT_OUT:
            case TOK_REDIRECT_IN:
            case TOK_REDIRECT_APPEND:
            case TOK_REDIRECT_FD:
            {
                if( !had_cmd )
                {
                    break;
                }
                tok_next( &tok );				
                break;
            }
                
            case TOK_PIPE:
            case TOK_BACKGROUND:
            case TOK_END:
            {
                had_cmd = false;
                cmd.empty();
                args.empty();
                arg_pos = -1;
                break;
            }
                
            case TOK_COMMENT:                    
            case TOK_ERROR:
            default:
            {
                break;				
            }			
        }
    }
    tok_destroy( &tok );
    
    /* Remember our command if we have one */
    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;
    }
    return had_cmd;
}
Example #16
0
/**
   The complete builtin. Used for specifying programmable
   tab-completions. Calls the functions in complete.c for any heavy
   lifting. Defined in builtin_complete.c
*/
static int builtin_complete(parser_t &parser, wchar_t **argv)
{
    ASSERT_IS_MAIN_THREAD();
    bool res=false;
    int argc=0;
    int result_mode=SHARED;
    int remove = 0;
    int authoritative = -1;

    wcstring short_opt;
    wcstring_list_t gnu_opt, old_opt;
    const wchar_t *comp=L"", *desc=L"", *condition=L"";

    bool do_complete = false;
    wcstring do_complete_param;

    wcstring_list_t cmd;
    wcstring_list_t path;
    wcstring_list_t wrap_targets;

    static int recursion_level=0;

    argc = builtin_count_args(argv);

    woptind=0;

    while (! res)
    {
        static const struct woption
                long_options[] =
        {
            { L"exclusive", no_argument, 0, 'x' },
            { L"no-files", no_argument, 0, 'f' },
            { L"require-parameter", no_argument, 0, 'r' },
            { L"path", required_argument, 0, 'p' },
            { L"command", required_argument, 0, 'c' },
            { L"short-option", required_argument, 0, 's' },
            { L"long-option", required_argument, 0, 'l' },
            { L"old-option", required_argument, 0, 'o' },
            { L"description", required_argument, 0, 'd' },
            { L"arguments", required_argument, 0, 'a' },
            { L"erase", no_argument, 0, 'e' },
            { L"unauthoritative", no_argument, 0, 'u' },
            { L"authoritative", no_argument, 0, 'A' },
            { L"condition", required_argument, 0, 'n' },
            { L"wraps", required_argument, 0, 'w' },
            { L"do-complete", optional_argument, 0, 'C' },
            { L"help", no_argument, 0, 'h' },
            { 0, 0, 0, 0 }
        };

        int opt_index = 0;

        int opt = wgetopt_long(argc,
                               argv,
                               L"a:c:p:s:l:o:d:frxeuAn:C::w:h",
                               long_options,
                               &opt_index);
        if (opt == -1)
            break;

        switch (opt)
        {
            case 0:
                if (long_options[opt_index].flag != 0)
                    break;
                append_format(stderr_buffer,
                              BUILTIN_ERR_UNKNOWN,
                              argv[0],
                              long_options[opt_index].name);
                builtin_print_help(parser, argv[0], stderr_buffer);


                res = true;
                break;

            case 'x':
                result_mode |= EXCLUSIVE;
                break;

            case 'f':
                result_mode |= NO_FILES;
                break;

            case 'r':
                result_mode |= NO_COMMON;
                break;

            case 'p':
            case 'c':
            {
                wcstring tmp;
                if (unescape_string(woptarg, &tmp, UNESCAPE_SPECIAL))
                {
                    if (opt=='p')
                        path.push_back(tmp);
                    else
                        cmd.push_back(tmp);
                }
                else
                {
                    append_format(stderr_buffer, L"%ls: Invalid token '%ls'\n", argv[0], woptarg);
                    res = true;
                }
                break;
            }

            case 'd':
                desc = woptarg;
                break;

            case 'u':
                authoritative=0;
                break;

            case 'A':
                authoritative=1;
                break;

            case 's':
                short_opt.append(woptarg);
                break;

            case 'l':
                gnu_opt.push_back(woptarg);
                break;

            case 'o':
                old_opt.push_back(woptarg);
                break;

            case 'a':
                comp = woptarg;
                break;

            case 'e':
                remove = 1;
                break;

            case 'n':
                condition = woptarg;
                break;
                
            case 'w':
                wrap_targets.push_back(woptarg);
                break;

            case 'C':
                do_complete = true;
                do_complete_param = woptarg ? woptarg : reader_get_buffer();
                break;

            case 'h':
                builtin_print_help(parser, argv[0], stdout_buffer);
                return 0;

            case '?':
                builtin_unknown_option(parser, argv[0], argv[woptind-1]);
                res = true;
                break;

        }

    }

    if (!res)
    {
        if (condition && wcslen(condition))
        {
            const wcstring condition_string = condition;
            parse_error_list_t errors;
            if (parse_util_detect_errors(condition_string, &errors, false /* do not accept incomplete */))
            {
                append_format(stderr_buffer,
                              L"%ls: Condition '%ls' contained a syntax error",
                              argv[0],
                              condition);
                for (size_t i=0; i < errors.size(); i++)
                {
                    append_format(stderr_buffer, L"\n%s: ", argv[0]);
                    stderr_buffer.append(errors.at(i).describe(condition_string));
                }
                res = true;
            }
        }
    }

    if (!res)
    {
        if (comp && wcslen(comp))
        {
            wcstring prefix;
            if (argv[0])
            {
                prefix.append(argv[0]);
                prefix.append(L": ");
            }

            wcstring err_text;
            if (parser.detect_errors_in_argument_list(comp, &err_text, prefix.c_str()))
            {
                append_format(stderr_buffer,
                              L"%ls: Completion '%ls' contained a syntax error\n",
                              argv[0],
                              comp);
                stderr_buffer.append(err_text);
                stderr_buffer.push_back(L'\n');
                res = true;
            }
        }
    }

    if (!res)
    {
        if (do_complete)
        {
            const wchar_t *token;

            parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, 0, 0);
            
            /* Create a scoped transient command line, so that bulitin_commandline will see our argument, not the reader buffer */
            builtin_commandline_scoped_transient_t temp_buffer(do_complete_param);

            if (recursion_level < 1)
            {
                recursion_level++;

                std::vector<completion_t> comp;
                complete(do_complete_param, comp, COMPLETION_REQUEST_DEFAULT);

                for (size_t i=0; i< comp.size() ; i++)
                {
                    const completion_t &next =  comp.at(i);

                    /* Make a fake commandline, and then apply the completion to it.  */
                    const wcstring faux_cmdline = token;
                    size_t tmp_cursor = faux_cmdline.size();
                    wcstring faux_cmdline_with_completion = completion_apply_to_command_line(next.completion, next.flags, faux_cmdline, &tmp_cursor, false);

                    /* completion_apply_to_command_line will append a space unless COMPLETE_NO_SPACE is set. We don't want to set COMPLETE_NO_SPACE because that won't close quotes. What we want is to close the quote, but not append the space. So we just look for the space and clear it. */
                    if (!(next.flags & COMPLETE_NO_SPACE) && string_suffixes_string(L" ", faux_cmdline_with_completion))
                    {
                        faux_cmdline_with_completion.resize(faux_cmdline_with_completion.size() - 1);
                    }

                    /* The input data is meant to be something like you would have on the command line, e.g. includes backslashes. The output should be raw, i.e. unescaped. So we need to unescape the command line. See #1127 */
                    unescape_string_in_place(&faux_cmdline_with_completion, UNESCAPE_DEFAULT);
                    stdout_buffer.append(faux_cmdline_with_completion);

                    /* Append any description */
                    if (! next.description.empty())
                    {
                        stdout_buffer.push_back(L'\t');
                        stdout_buffer.append(next.description);
                    }
                    stdout_buffer.push_back(L'\n');
                }

                recursion_level--;
            }
        }
        else if (woptind != argc)
        {
            append_format(stderr_buffer,
                          _(L"%ls: Too many arguments\n"),
                          argv[0]);
            builtin_print_help(parser, argv[0], stderr_buffer);

            res = true;
        }
        else if (cmd.empty() && path.empty())
        {
            /* No arguments specified, meaning we print the definitions of
             * all specified completions to stdout.*/
            complete_print(stdout_buffer);
        }
        else
        {
            int flags = COMPLETE_AUTO_SPACE;
        
            if (remove)
            {
                builtin_complete_remove(cmd,
                                        path,
                                        short_opt.c_str(),
                                        gnu_opt,
                                        old_opt);
                
            }
            else
            {
                builtin_complete_add(cmd,
                                     path,
                                     short_opt.c_str(),
                                     gnu_opt,
                                     old_opt,
                                     result_mode,
                                     authoritative,
                                     condition,
                                     comp,
                                     desc,
                                     flags);
            }
            
            // Handle wrap targets (probably empty)
            // We only wrap commands, not paths
            for (size_t w=0; w < wrap_targets.size(); w++)
            {
                const wcstring &wrap_target = wrap_targets.at(w);
                for (size_t i=0; i < cmd.size(); i++)
                {
                    
                    (remove ? complete_remove_wrapper : complete_add_wrapper)(cmd.at(i), wrap_target);
                }
            }
        }
    }

    return res ? 1 : 0;
}
Example #17
0
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;

    /* Parse the string */    
    wcstring parsed_command;
    wcstring_list_t parsed_arguments;
    int parsed_last_arg_pos = -1;
    if (! autosuggest_parse_command(item.str(), &parsed_command, &parsed_arguments, &parsed_last_arg_pos))
        return false;

    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) {
                suggestionOK = false;
            } else {
                wcstring path;
                bool can_cd = path_get_cdpath(dir, &path, working_directory.c_str(), vars);
                if (! can_cd) {
                    suggestionOK = false;
                } else if (paths_are_same_file(working_directory, path)) {
                    /* Don't suggest the working directory as the path! */
                    suggestionOK = false;
                } else {
                    suggestionOK = true;
                }
            }
        }        
    } 
  
    /* If not handled specially, handle it here */
    if (! handled) {
        bool cmd_ok = false;

        if (path_get_path(parsed_command, NULL))
        {
            cmd_ok = true;
        }
        else if (builtin_exists(parsed_command) || function_exists_no_autoload(parsed_command, vars))
        {
            cmd_ok = true;
        }

        if (cmd_ok) {
            const path_list_t &paths = item.get_required_paths();
            if (paths.empty()) {
                suggestionOK= true;
            }
            else {
                detector.potential_paths = paths;
                suggestionOK = detector.paths_are_valid(paths);
            }
        }
    }

    return suggestionOK;
}
Example #18
0
int main(int argc, char *argv[]) {
    program_name = L"fish_indent";
    set_main_thread();
    setup_fork_guards();
    // Using the user's default locale could be a problem if it doesn't use UTF-8 encoding. That's
    // because the fish project assumes Unicode UTF-8 encoding in all of its scripts.
    //
    // TODO: Auto-detect the encoding of the script. We should look for a vim style comment
    // (e.g., "# vim: set fileencoding=<encoding-name>:") or an emacs style comment
    // (e.g., "# -*- coding: <encoding-name> -*-").
    setlocale(LC_ALL, "");
    env_init();

    // Types of output we support.
    enum {
        output_type_plain_text,
        output_type_file,
        output_type_ansi,
        output_type_html
    } output_type = output_type_plain_text;
    const char *output_location = "";
    bool do_indent = true;

    const char *short_opts = "+d:hvwiD:";
    const struct option long_opts[] = {{"debug-level", required_argument, NULL, 'd'},
                                       {"debug-stack-frames", required_argument, NULL, 'D'},
                                       {"dump-parse-tree", no_argument, NULL, 'P'},
                                       {"no-indent", no_argument, NULL, 'i'},
                                       {"help", no_argument, NULL, 'h'},
                                       {"version", no_argument, NULL, 'v'},
                                       {"write", no_argument, NULL, 'w'},
                                       {"html", no_argument, NULL, 1},
                                       {"ansi", no_argument, NULL, 2},
                                       {NULL, 0, NULL, 0}};

    int opt;
    while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
        switch (opt) {
            case 'P': {
                dump_parse_tree = true;
                break;
            }
            case 'h': {
                print_help("fish_indent", 1);
                exit(0);
                break;
            }
            case 'v': {
                fwprintf(stderr, _(L"%ls, version %s\n"), program_name, get_fish_version());
                exit(0);
                break;
            }
            case 'w': {
                output_type = output_type_file;
                break;
            }
            case 'i': {
                do_indent = false;
                break;
            }
            case 1: {
                output_type = output_type_html;
                break;
            }
            case 2: {
                output_type = output_type_ansi;
                break;
            }
            case 'd': {
                char *end;
                long tmp;

                errno = 0;
                tmp = strtol(optarg, &end, 10);

                if (tmp >= 0 && tmp <= 10 && !*end && !errno) {
                    debug_level = (int)tmp;
                } else {
                    fwprintf(stderr, _(L"Invalid value '%s' for debug-level flag"), optarg);
                    exit(1);
                }
                break;
            }
            case 'D': {
                char *end;
                long tmp;

                errno = 0;
                tmp = strtol(optarg, &end, 10);

                if (tmp > 0 && tmp <= 128 && !*end && !errno) {
                    debug_stack_frames = (int)tmp;
                } else {
                    fwprintf(stderr, _(L"Invalid value '%s' for debug-stack-frames flag"), optarg);
                    exit(1);
                }
                break;
            }
            default: {
                // We assume getopt_long() has already emitted a diagnostic msg.
                exit(1);
                break;
            }
        }
    }

    argc -= optind;
    argv += optind;

    wcstring src;
    if (argc == 0) {
        if (output_type == output_type_file) {
            fwprintf(stderr, _(L"Expected file path to read/write for -w:\n\n $ %ls -w foo.fish\n"),
                     program_name);
            exit(1);
        }
        src = read_file(stdin);
    } else if (argc == 1) {
        FILE *fh = fopen(*argv, "r");
        if (fh) {
            src = read_file(fh);
            fclose(fh);
            output_location = *argv;
        } else {
            fwprintf(stderr, _(L"Opening \"%s\" failed: %s\n"), *argv, strerror(errno));
            exit(1);
        }
    } else {
        fwprintf(stderr, _(L"Too many arguments\n"));
        exit(1);
    }

    const wcstring output_wtext = prettify(src, do_indent);

    // Maybe colorize.
    std::vector<highlight_spec_t> colors;
    if (output_type != output_type_plain_text) {
        highlight_shell_no_io(output_wtext, colors, output_wtext.size(), NULL,
                              env_vars_snapshot_t::current());
    }

    std::string colored_output;
    switch (output_type) {
        case output_type_plain_text: {
            colored_output = no_colorize(output_wtext);
            break;
        }
        case output_type_file: {
            FILE *fh = fopen(output_location, "w");
            if (fh) {
                fputws(output_wtext.c_str(), fh);
                fclose(fh);
                exit(0);
            } else {
                fwprintf(stderr, _(L"Opening \"%s\" failed: %s\n"), output_location,
                         strerror(errno));
                exit(1);
            }
            break;
        }
        case output_type_ansi: {
            colored_output = ansi_colorize(output_wtext, colors);
            break;
        }
        case output_type_html: {
            colored_output = html_colorize(output_wtext, colors);
            break;
        }
    }

    fputws(str2wcstring(colored_output).c_str(), stdout);
    return 0;
}
Example #19
0
bool builtin_data_t::operator<(const wcstring &other) const {
    return std::wcscmp(this->name, other.c_str()) < 0;
}
Example #20
0
// PCA This function does I/O, (calls is_potential_path, path_get_path, maybe others) and so ought to only run on a background thread
void highlight_shell( const wcstring &buff, std::vector<int> &color, int pos, wcstring_list_t *error, const env_vars_snapshot_t &vars )
{
    ASSERT_IS_BACKGROUND_THREAD();
    
    const size_t length = buff.size();
    assert(buff.size() == color.size());


	if( length == 0 )
		return;
	
    std::fill(color.begin(), color.end(), -1);

    /* Do something sucky and get the current working directory on this background thread. This should really be passed in. Note that we also need this as a vector (of one directory). */
    const wcstring working_directory = get_working_directory();

    /* Tokenize the string */
    tokenize(buff.c_str(), color, pos, error, working_directory, vars);

	/*
	  Locate and syntax highlight cmdsubsts recursively
	*/

	wchar_t * const subbuff = wcsdup(buff.c_str());
    wchar_t * subpos = subbuff;
	int done=0;
	
	while( 1 )
	{
		wchar_t *begin, *end;
    
		if( parse_util_locate_cmdsubst(subpos, &begin, &end, 1) <= 0)
		{
			break;
		}
		
		if( !*end )
			done=1;
		else
			*end=0;
		
        //our subcolors start at color + (begin-subbuff)+1
        size_t start = begin - subbuff + 1, len = wcslen(begin + 1);
        std::vector<int> subcolors(len, -1);
        
		highlight_shell( begin+1, subcolors, -1, error, vars );
        
        // insert subcolors
        std::copy(subcolors.begin(), subcolors.end(), color.begin() + start);
        
        // highlight the end of the subcommand
        assert(end >= subbuff);
        if ((size_t)(end - subbuff) < length) {
            color.at(end-subbuff)=HIGHLIGHT_OPERATOR;
        }
		
		if( done )
			break;
		
		subpos = end+1;
	}
    free(subbuff);

	/*
	  The highlighting code only changes the first element when the
	  color changes. This fills in the rest.
	*/
	int last_val=0;
	for( size_t i=0; i < buff.size(); i++ )
	{
		if( color.at(i) >= 0 )
			last_val = color.at(i);
		else
			color.at(i) = last_val;
	}
    
	/*
	  Color potentially valid paths in a special path color if they
	  are the current token.
      For reasons that I don't yet understand, it's required that pos be allowed to be length (e.g. when backspacing).
	*/
	if( pos >= 0 && (size_t)pos <= length )
	{
		
        const wchar_t *cbuff = buff.c_str();
		const wchar_t *tok_begin, *tok_end;
		parse_util_token_extent( cbuff, pos, &tok_begin, &tok_end, 0, 0 );
		if( tok_begin && tok_end )
		{
			wcstring token(tok_begin, tok_end-tok_begin);
			const wcstring_list_t working_directory_list(1, working_directory);
			if (unescape_string(token, 1) && is_potential_path(token, working_directory_list, PATH_EXPAND_TILDE))
			{
				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)) {
                        color.at(i) |= HIGHLIGHT_VALID_PATH;
                    }
				}
			}
		}
	}
	

	highlight_universal_internal( buff, color, pos );

	/*
	  Spaces should not be highlighted at all, since it makes cursor look funky in some terminals
	*/
	for( size_t i=0; i < buff.size(); i++ )
	{
		if( iswspace(buff.at(i)) )
		{
			color.at(i)=0;
		}
	}
}
Example #21
0
static bool path_get_path_core(const wcstring &cmd, wcstring *out_path, const env_var_t &bin_path_var)
{
    int err = ENOENT;

    debug(3, L"path_get_path( '%ls' )", cmd.c_str());

    /* If the command has a slash, it must be a full path */
    if (cmd.find(L'/') != wcstring::npos)
    {
        if (waccess(cmd, X_OK)==0)
        {
            struct stat buff;
            if (wstat(cmd, &buff))
            {
                return false;
            }

            if (S_ISREG(buff.st_mode))
            {
                if (out_path)
                    out_path->assign(cmd);
                return true;
            }
            else
            {
                errno = EACCES;
                return false;
            }
        }
        else
        {
            return false;
        }

    }
    else
    {
        wcstring bin_path;
        if (! bin_path_var.missing())
        {
            bin_path = bin_path_var;
        }
        else
        {
            if (contains(PREFIX L"/bin", L"/bin", L"/usr/bin"))
            {
                bin_path = L"/bin" ARRAY_SEP_STR L"/usr/bin";
            }
            else
            {
                bin_path = L"/bin" ARRAY_SEP_STR L"/usr/bin" ARRAY_SEP_STR PREFIX L"/bin";
            }
        }

        wcstring nxt_path;
        wcstokenizer tokenizer(bin_path, ARRAY_SEP_STR);
        while (tokenizer.next(nxt_path))
        {
            if (nxt_path.empty())
                continue;
            append_path_component(nxt_path, cmd);
            if (waccess(nxt_path, X_OK)==0)
            {
                struct stat buff;
                if (wstat(nxt_path, &buff)==-1)
                {
                    if (errno != EACCES)
                    {
                        wperror(L"stat");
                    }
                    continue;
                }
                if (S_ISREG(buff.st_mode))
                {
                    if (out_path)
                        out_path->swap(nxt_path);
                    return true;
                }
                err = EACCES;

            }
            else
            {
                switch (errno)
                {
                    case ENOENT:
                    case ENAMETOOLONG:
                    case EACCES:
                    case ENOTDIR:
                        break;
                    default:
                    {
                        debug(1,
                              MISSING_COMMAND_ERR_MSG,
                              nxt_path.c_str());
                        wperror(L"access");
                    }
                }
            }
        }
    }

    errno = err;
    return false;
}
Example #22
0
/* Tests whether the specified string cpath is the prefix of anything we could cd to. directories is a list of possible parent directories (typically either the working directory, or the cdpath). This does I/O!

   We expect the path to already be unescaped.
*/
bool is_potential_path(const wcstring &const_path, const wcstring_list_t &directories, path_flags_t flags, wcstring *out_path)
{
    ASSERT_IS_BACKGROUND_THREAD();
    
    const bool require_dir = !! (flags & PATH_REQUIRE_DIR);
    wcstring clean_path;
	int has_magic = 0;
	bool result = false;
    
    wcstring path(const_path);
    if (flags & PATH_EXPAND_TILDE)
        expand_tilde(path);    
    
    //	debug( 1, L"%ls -> %ls ->%ls", path, tilde, unescaped );
    
    for( size_t i=0; i < path.size(); i++)
    {
        wchar_t c = path.at(i);
        switch( c )
        {
            case PROCESS_EXPAND:
            case VARIABLE_EXPAND:
            case VARIABLE_EXPAND_SINGLE:
            case BRACKET_BEGIN:
            case BRACKET_END:
            case BRACKET_SEP:
            case ANY_CHAR:
            case ANY_STRING:
            case ANY_STRING_RECURSIVE:
            {
                has_magic = 1;
                break;		
            }
				
            case INTERNAL_SEPARATOR:
            {
                break;
            }
				
            default:
            {
                clean_path.push_back(c);
                break;
            }
				
        }
        
    }
    
    if( ! has_magic && ! clean_path.empty() )
    {
        /* Don't test the same path multiple times, which can happen if the path is absolute and the CDPATH contains multiple entries */
        std::set<wcstring> checked_paths;
        
        /* 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++) {
            const wcstring &wd = directories.at(wd_idx);
            
            const wcstring abs_path = apply_working_directory(clean_path, wd);
            
            /* Skip this if it's empty or we've already checked it */
            if (abs_path.empty() || checked_paths.count(abs_path))
                continue;
            checked_paths.insert(abs_path);
            
            /* If we end with a slash, then it must be a directory */
            bool must_be_full_dir = abs_path.at(abs_path.size()-1) == L'/';
            if (must_be_full_dir) 
            {
                struct stat buf;
                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)
                        *out_path = clean_path;
                }
            }
            else
            {
                DIR *dir = NULL;
                
                /* We do not end with a slash; it does not have to be a directory */
                const wcstring dir_name = wdirname(abs_path);
                const wcstring base_name = wbasename(abs_path);
                if (dir_name == L"/" && base_name == L"/")
                {
                    result = true;
                    if (out_path)
                        *out_path = clean_path;
                }
                else if ((dir = wopendir(dir_name))) {
                    // We opened the dir_name; look for a string where the base name prefixes it
                    wcstring ent;
                    
                    // Check if we're case insensitive
                    bool case_insensitive = fs_is_case_insensitive(dir_name, dirfd(dir), case_sensitivity_cache);
                    
                    // Don't ask for the is_dir value unless we care, because it can cause extra filesystem acces */
                    bool is_dir = false;
                    while (wreaddir_resolving(dir, dir_name, ent, require_dir ? &is_dir : NULL))
                    {                    

                        /* Determine which function to call to check for prefixes */
                        bool (*prefix_func)(const wcstring &, const wcstring &);
                        if (case_insensitive) {
                            prefix_func = string_prefixes_string_case_insensitive;
                        } else {
                            prefix_func = string_prefixes_string;
                        }

                        if (prefix_func(base_name, ent) && (! require_dir || is_dir))
                        {
                            result = true;
                            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)) {
                                    out_path->append(path_base);
                                    if (! string_suffixes_string(L"/", *out_path))
                                        out_path->push_back(L'/');
                                }
                                out_path->append(ent);
                                /* We actually do want a trailing / for directories, since it makes autosuggestion a bit nicer */
                                if (is_dir)
                                    out_path->push_back(L'/');
                            }
                            break;
                        }
                    }
                    closedir(dir);
                }
            }
        }
    }
    return result;
}
Example #23
0
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;
    return true;
}
Example #24
0
/**
   Perform quote and parenthesis highlighting on the specified string.
*/
static void highlight_universal_internal( const wcstring &buffstr,
										 std::vector<int> &color, 
										 int pos )
{	
    assert(buffstr.size() == color.size());
	if( (pos >= 0) && ((size_t)pos < buffstr.size()) )
	{
		
		/*
		  Highlight matching quotes
		*/
		if( (buffstr.at(pos) == L'\'') || (buffstr.at(pos) == L'\"') )
		{
			std::vector<long> lst;
		
			int level=0;
			wchar_t prev_q=0;
		
            const wchar_t * const buff = buffstr.c_str();
			const wchar_t *str = buff;

			int match_found=0;
		
			while(*str)
			{
				switch( *str )
				{
					case L'\\':
						str++;
						break;
					case L'\"':
					case L'\'':
						if( level == 0 )
						{
							level++;
                            lst.push_back((long)(str-buff));
							prev_q = *str;
						}
						else
						{
							if( prev_q == *str )
							{
								long pos1, pos2;
							
								level--;
                                pos1 = lst.back();
								pos2 = str-buff;
								if( pos1==pos || pos2==pos )
								{
									color.at(pos1)|=HIGHLIGHT_MATCH<<16;
									color.at(pos2)|=HIGHLIGHT_MATCH<<16;
									match_found = 1;
									
								}
								prev_q = *str==L'\"'?L'\'':L'\"';
							}
							else
							{
								level++;
                                lst.push_back((long)(str-buff));
								prev_q = *str;
							}
						}
					
						break;
				}
				if( (*str == L'\0'))
					break;

				str++;
			}
		
			if( !match_found )
				color.at(pos) = HIGHLIGHT_ERROR<<16;
		}

		/*
		  Highlight matching parenthesis
		*/
        const wchar_t c = buffstr.at(pos);
		if( wcschr( L"()[]{}", c ) )
		{
			int step = wcschr(L"({[", c)?1:-1;
			wchar_t dec_char = *(wcschr( L"()[]{}", c ) + step);
			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) {
                const wchar_t test_char = buffstr.at(i); 
                if( test_char == inc_char )
					level++;
				if( test_char == dec_char )
					level--;
				if( level == 0 )
				{
					long pos2 = i;
					color.at(pos)|=HIGHLIGHT_MATCH<<16;
					color.at(pos2)|=HIGHLIGHT_MATCH<<16;
					match_found=1;
					break;
				}
			}
			
			if( !match_found )
				color[pos] = HIGHLIGHT_ERROR<<16;
		}
	}
}
Example #25
0
/**
   Insert the specified number of tabs into the output buffer
 */
static void insert_tabs(wcstring &out, int indent)
{
    if (indent > 0)
        out.append((size_t)indent, L'\t');

}
Example #26
0
void highlight_universal( const wcstring &buff, std::vector<int> &color, int pos, wcstring_list_t *error, const env_vars_snapshot_t &vars )
{
    assert(buff.size() == color.size());
    std::fill(color.begin(), color.end(), 0);	
	highlight_universal_internal( buff, color, pos );
}
Example #27
0
/**
   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! */
	size_t i;
	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( 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) == -1 )
        {
            /*
              Do nothing on failiure
            */
        }

    }

    if (really_load) {
        return reloaded;
    } else {
        return found_file || has_script_source;
    }
}
Example #28
0
/**
   Highlight operators (such as $, ~, %, as well as escaped characters.
*/
static void highlight_param( const wcstring &buffstr, std::vector<int> &colors, int pos, wcstring_list_t *error )
{
    const wchar_t * const buff = buffstr.c_str();
	enum {e_unquoted, e_single_quoted, e_double_quoted} mode = e_unquoted;
	size_t in_pos, len = buffstr.size();
	int bracket_count=0;
	int normal_status = colors.at(0);
	
	for (in_pos=0; in_pos<len; in_pos++)
	{
		wchar_t c = buffstr.at(in_pos);
		switch( mode )
		{
                /*
                 Mode 0 means unquoted string
                 */
			case e_unquoted:
			{
				if( c == L'\\' )
				{
					size_t start_pos = in_pos;
					in_pos++;
					
					if( wcschr( L"~%", buff[in_pos] ) )
					{
						if( in_pos == 1 )
						{
							colors.at(start_pos) = HIGHLIGHT_ESCAPE;
							colors.at(in_pos+1) = normal_status;
						}
					}
					else if( buff[in_pos]==L',' )
					{
						if( bracket_count )
						{
							colors.at(start_pos) = HIGHLIGHT_ESCAPE;
							colors.at(in_pos+1) = normal_status;
						}
					}
					else if( wcschr( L"abefnrtv*?$(){}[]'\"<>^ \\#;|&", buff[in_pos] ) )
					{
						colors.at(start_pos)=HIGHLIGHT_ESCAPE;
						colors.at(in_pos+1)=normal_status;
					}
					else if( wcschr( L"c", buff[in_pos] ) )
                    {
						colors.at(start_pos)=HIGHLIGHT_ESCAPE;
                        if (in_pos+2 < colors.size())
                            colors.at(in_pos+2)=normal_status;
					}
					else if( wcschr( L"uUxX01234567", buff[in_pos] ) )
					{
						int i;
						long long res=0;
						int chars=2;
						int base=16;
						
						wchar_t max_val = ASCII_MAX;
						
						switch( buff[in_pos] )
						{
							case L'u':
							{
								chars=4;
								max_val = UCS2_MAX;
								break;
							}
                                
							case L'U':
							{
								chars=8;
								max_val = WCHAR_MAX;
								break;
							}
                                
							case L'x':
							{
								break;
							}
                                
							case L'X':
							{
								max_val = BYTE_MAX;
								break;
							}
                                
							default:
							{
								base=8;
								chars=3;
								in_pos--;
								break;
							}								
						}
						
						for( i=0; i<chars; i++ )
						{
							int d = convert_digit( buff[++in_pos],base);
							
							if( d < 0 )
							{
								in_pos--;
								break;
							}
							
							res=(res*base)|d;
						}
                        
						if( (res <= max_val) )
						{
							colors.at(start_pos) = HIGHLIGHT_ESCAPE;
							colors.at(in_pos+1) = normal_status;								
						}
						else
						{	
							colors.at(start_pos) = HIGHLIGHT_ERROR;
							colors.at(in_pos+1) = normal_status;								
						}
					}
                    
				}
				else 
				{
					switch( buff[in_pos]){
						case L'~':
						case L'%':
						{
							if( in_pos == 0 )
							{
								colors.at(in_pos) = HIGHLIGHT_OPERATOR;
								colors.at(in_pos+1) = normal_status;
							}
							break;
						}
                            
						case L'$':
						{
							wchar_t n = buff[in_pos+1];							
							colors.at(in_pos) = (n==L'$'||wcsvarchr(n))? HIGHLIGHT_OPERATOR:HIGHLIGHT_ERROR;
							colors.at(in_pos+1) = normal_status;								
							break;
						}
                            
                            
						case L'*':
						case L'?':
						case L'(':
						case L')':
						{
							colors.at(in_pos) = HIGHLIGHT_OPERATOR;
							colors.at(in_pos+1) = normal_status;
							break;
						}
                            
						case L'{':
						{
							colors.at(in_pos) = HIGHLIGHT_OPERATOR;
							colors.at(in_pos+1) = normal_status;
							bracket_count++;
							break;					
						}
                            
						case L'}':
						{
							colors.at(in_pos) = HIGHLIGHT_OPERATOR;
							colors.at(in_pos+1) = normal_status;
							bracket_count--;
							break;						
						}
                            
						case L',':
						{
							if( bracket_count )
							{
								colors.at(in_pos) = HIGHLIGHT_OPERATOR;
								colors.at(in_pos+1) = normal_status;
							}
                            
							break;					
						}
                            
						case L'\'':
						{
							colors.at(in_pos) = HIGHLIGHT_QUOTE;
							mode = e_single_quoted;
							break;					
						}
                            
						case L'\"':
						{
							colors.at(in_pos) = HIGHLIGHT_QUOTE;
							mode = e_double_quoted;
							break;
						}
                            
					}
				}		
				break;
			}
                
                /*
                 Mode 1 means single quoted string, i.e 'foo'
                 */
			case e_single_quoted:
			{
				if( c == L'\\' )
				{
					int start_pos = in_pos;
					switch( buff[++in_pos] )
					{
						case '\\':
						case L'\'':
						{
							colors.at(start_pos) = HIGHLIGHT_ESCAPE;
							colors.at(in_pos+1) = HIGHLIGHT_QUOTE;
							break;
						}
                            
						case 0:
						{
							return;
						}
                            
					}
					
				}
				if( c == L'\'' )
				{
					mode = e_unquoted;
					colors.at(in_pos+1) = normal_status;
				}
				
				break;
			}
                
                /*
                 Mode 2 means double quoted string, i.e. "foo"
                 */
			case e_double_quoted:
			{
				switch( c )
				{
					case '"':
					{
						mode = e_unquoted;
						colors.at(in_pos+1) = normal_status;
						break;
					}
                        
					case '\\':
					{
						int start_pos = in_pos;
						switch( buff[++in_pos] )
						{
							case L'\0':
							{
								return;
							}
                                
							case '\\':
							case L'$':
							case '"':
							{
								colors.at(start_pos) = HIGHLIGHT_ESCAPE;
								colors.at(in_pos+1) = HIGHLIGHT_QUOTE;
								break;
							}
						}
						break;
					}
                        
					case '$':
					{
						wchar_t n = buff[in_pos+1];
						colors.at(in_pos) = (n==L'$'||wcsvarchr(n))? HIGHLIGHT_OPERATOR:HIGHLIGHT_ERROR;
						colors.at(in_pos+1) = HIGHLIGHT_QUOTE;								
						break;
					}
                        
				}						
				break;
			}
		}
	}
}
Example #29
0
static int process_clean_after_marking(bool allow_interactive) {
    ASSERT_IS_MAIN_THREAD();
    job_t *jnext;
    int found = 0;

    // this function may fire an event handler, we do not want to call ourselves recursively (to avoid
    // infinite recursion).
    static bool locked = false;
    if (locked) {
        return 0;
    }
    locked = true;

    // this may be invoked in an exit handler, after the TERM has been torn down
    // don't try to print in that case (#3222)
    const bool interactive = allow_interactive && cur_term != NULL;


    job_iterator_t jobs;
    const size_t job_count = jobs.count();
    jnext = jobs.next();
    while (jnext) {
        job_t *j = jnext;
        jnext = jobs.next();

        // If we are reaping only jobs who do not need status messages sent to the console, do not
        // consider reaping jobs that need status messages.
        if ((!j->get_flag(JOB_SKIP_NOTIFICATION)) && (!interactive) &&
            (!j->get_flag(JOB_FOREGROUND))) {
            continue;
        }

        for (const process_ptr_t &p : j->processes) {
            int s;
            if (!p->completed) continue;

            if (!p->pid) continue;

            s = p->status;

            // TODO: The generic process-exit event is useless and unused.
            // Remove this in future.
            proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, p->pid,
                            (WIFSIGNALED(s) ? -1 : WEXITSTATUS(s)));

            // Ignore signal SIGPIPE.We issue it ourselves to the pipe writer when the pipe reader
            // dies.
            if (!WIFSIGNALED(s) || WTERMSIG(s) == SIGPIPE) {
                continue;
            }

            // Handle signals other than SIGPIPE.
            int proc_is_job = (p->is_first_in_job && p->is_last_in_job);
            if (proc_is_job) j->set_flag(JOB_NOTIFIED, true);
            if (j->get_flag(JOB_SKIP_NOTIFICATION)) {
                continue;
            }

            // Print nothing if we get SIGINT in the foreground process group, to avoid spamming
            // obvious stuff on the console (#1119). If we get SIGINT for the foreground
            // process, assume the user typed ^C and can see it working. It's possible they
            // didn't, and the signal was delivered via pkill, etc., but the SIGINT/SIGTERM
            // distinction is precisely to allow INT to be from a UI
            // and TERM to be programmatic, so this assumption is keeping with the design of
            // signals. If echoctl is on, then the terminal will have written ^C to the console.
            // If off, it won't have. We don't echo ^C either way, so as to respect the user's
            // preference.
            if (WTERMSIG(p->status) != SIGINT || !j->get_flag(JOB_FOREGROUND)) {
                if (proc_is_job) {
                    // We want to report the job number, unless it's the only job, in which case
                    // we don't need to.
                    const wcstring job_number_desc =
                        (job_count == 1) ? wcstring() : format_string(_(L"Job %d, "), j->job_id);
                    fwprintf(stdout, _(L"%ls: %ls\'%ls\' terminated by signal %ls (%ls)"),
                             program_name, job_number_desc.c_str(),
                             truncate_command(j->command()).c_str(), sig2wcs(WTERMSIG(p->status)),
                             signal_get_desc(WTERMSIG(p->status)));
                } else {
                    const wcstring job_number_desc =
                        (job_count == 1) ? wcstring() : format_string(L"from job %d, ", j->job_id);
                    const wchar_t *fmt =
                        _(L"%ls: Process %d, \'%ls\' %ls\'%ls\' terminated by signal %ls (%ls)");
                    fwprintf(stdout, fmt, program_name, p->pid, p->argv0(), job_number_desc.c_str(),
                             truncate_command(j->command()).c_str(), sig2wcs(WTERMSIG(p->status)),
                             signal_get_desc(WTERMSIG(p->status)));
                }

                if (cur_term != NULL) {
                    tputs(clr_eol, 1, &writeb);
                } else {
                    fwprintf(stdout, L"\e[K");  // no term set up - do clr_eol manually
                }
                fwprintf(stdout, L"\n");
            }
            found = 1;
            p->status = 0;  // clear status so it is not reported more than once
        }

        // If all processes have completed, tell the user the job has completed and delete it from
        // the active job list.
        if (job_is_completed(j)) {
            if (!j->get_flag(JOB_FOREGROUND) && !j->get_flag(JOB_NOTIFIED) &&
                !j->get_flag(JOB_SKIP_NOTIFICATION)) {
                format_job_info(j, JOB_ENDED);
                found = 1;
            }
            // TODO: The generic process-exit event is useless and unused.
            // Remove this in future.
            // Don't fire the exit-event for jobs with pgid -2.
            // That's our "sentinel" pgid, for jobs that don't (yet) have a pgid,
            // or jobs that consist entirely of builtins (and hence don't have a process).
            // This causes issues if fish is PID 2, which is quite common on WSL. See #4582.
            if (j->pgid != -2) {
                proc_fire_event(L"JOB_EXIT", EVENT_EXIT, -j->pgid, 0);
            }
            proc_fire_event(L"JOB_EXIT", EVENT_JOB_ID, j->job_id, 0);

            job_remove(j);
        } else if (job_is_stopped(j) && !j->get_flag(JOB_NOTIFIED)) {
            // Notify the user about newly stopped jobs.
            if (!j->get_flag(JOB_SKIP_NOTIFICATION)) {
                format_job_info(j, JOB_STOPPED);
                found = 1;
            }
            j->set_flag(JOB_NOTIFIED, true);
        }
    }

    if (found) fflush(stdout);

    locked = false;

    return found;
}
Example #30
0
static bool string_sort_predicate(const wcstring& d1, const wcstring& d2)
{
    return wcsfilecmp(d1.c_str(), d2.c_str()) < 0;
}