Пример #1
0
/// Returns the length of the "shared prefix" of the two lines, which is the run of matching text
/// and colors. If the prefix ends on a combining character, do not include the previous character
/// in the prefix.
static size_t line_shared_prefix(const line_t &a, const line_t &b) {
    size_t idx, max = std::min(a.size(), b.size());
    for (idx = 0; idx < max; idx++) {
        wchar_t ac = a.char_at(idx), bc = b.char_at(idx);
        if (fish_wcwidth(ac) < 1 || fish_wcwidth(bc) < 1) {
            // Possible combining mark, return one index prior.
            if (idx > 0) idx--;
            break;
        }

        // We're done if the text or colors are different.
        if (ac != bc || a.color_at(idx) != b.color_at(idx)) break;
    }
    return idx;
}
Пример #2
0
/// Print the specified string, but use at most the specified amount of space. If the whole string
/// can't be fitted, ellipsize it.
///
/// \param str the string to print
/// \param color the color to apply to every printed character
/// \param max the maximum space that may be used for printing
/// \param has_more if this flag is true, this is not the entire string, and the string should be
/// ellisiszed even if the string fits but takes up the whole space.
static int print_max(const wcstring &str, highlight_spec_t color, int max, bool has_more,
                     line_t *line) {
    int written = 0;
    for (size_t i = 0; i < str.size(); i++) {
        wchar_t c = str.at(i);

        if (written + fish_wcwidth(c) > max) break;
        if ((written + fish_wcwidth(c) == max) && (has_more || i + 1 < str.size())) {
            line->append(ellipsis_char, color);
            written += fish_wcwidth(ellipsis_char);
            break;
        }

        line->append(c, color);
        written += fish_wcwidth(c);
    }
    return written;
}
Пример #3
0
/// Like fish_wcwidth, but returns 0 for control characters instead of -1.
static int fish_wcwidth_min_0(wchar_t widechar) { return std::max(0, fish_wcwidth(widechar)); }
Пример #4
0
void s_write( screen_t *s,
	      const wchar_t *prompt,
	      const wchar_t *commandline, 
	      size_t explicit_len,
	      const int *c, 
	      const int *indent,
	      size_t cursor_pos )
{
	screen_data_t::cursor_t cursor_arr;

	size_t prompt_width;
	size_t screen_width;

	int current_line_width = 0, newline_count = 0, explicit_portion_width = 0;
    size_t max_line_width = 0;
	
	CHECK( s, );
	CHECK( prompt, );
	CHECK( commandline, );
	CHECK( c, );
	CHECK( indent, );

	/*
	  If we are using a dumb terminal, don't try any fancy stuff,
	  just print out the text.
	 */
	if( is_dumb() )
	{
		char *prompt_narrow = wcs2str( prompt );
		char *buffer_narrow = wcs2str( commandline );
		
		write_loop( 1, "\r", 1 );
		write_loop( 1, prompt_narrow, strlen( prompt_narrow ) );
		write_loop( 1, buffer_narrow, strlen( buffer_narrow ) );

		free( prompt_narrow );
		free( buffer_narrow );
		
		return;
	}
	
	prompt_width = calc_prompt_width( prompt );
	screen_width = common_get_width();

	s_check_status( s );

	/*
	  Ignore prompts wider than the screen - only print a two
	  character placeholder...

	  It would be cool to truncate the prompt, but because it can
	  contain escape sequences, this is harder than you'd think.
	*/
	if( prompt_width >= screen_width )
	{
		prompt = L"> ";
		prompt_width = 2;
	}
	
	/*
	  Completely ignore impossibly small screens
	*/
	if( screen_width < 4 )
	{
		return;
	}

	/*
	  Check if we are overflowing
	 */
    size_t last_char_that_fits = 0;
	for( size_t i=0; commandline[i]; i++ )
	{
		if( commandline[i] == L'\n' )
		{
			if( current_line_width > max_line_width )
				max_line_width = current_line_width;
			current_line_width = indent[i]*INDENT_STEP;
            newline_count++;
		}
		else
		{
            int width = fish_wcwidth(commandline[i]);
			current_line_width += width;
            if (i < explicit_len)
                explicit_portion_width += width;
                
            if (prompt_width + current_line_width < screen_width)
                last_char_that_fits = i;
		}
	}
	if( current_line_width > max_line_width )
		max_line_width = current_line_width;

    s->desired.resize(0);
	s->desired.cursor.x = s->desired.cursor.y = 0;
    
    /* If we cannot fit with the autosuggestion, but we can fit without it, truncate the autosuggestion. We limit this check to just one line to avoid confusion; not sure how well this would work with multiple lines */
    wcstring truncated_autosuggestion_line;
    if (newline_count == 0 && prompt_width + max_line_width >= screen_width && prompt_width + explicit_portion_width < screen_width)
    {
        assert(screen_width - prompt_width >= 1);
        max_line_width = screen_width - prompt_width - 1;
        truncated_autosuggestion_line = wcstring(commandline, 0, last_char_that_fits);
        commandline = truncated_autosuggestion_line.c_str();
    }
	for( size_t i=0; i<prompt_width; i++ )
	{
		s_desired_append_char( s, L' ', 0, 0, prompt_width );
	}

	/*
	  If overflowing, give the prompt its own line to improve the
	  situation.
	 */
	if( max_line_width + prompt_width >= screen_width )
	{
		s_desired_append_char( s, L'\n', 0, 0, 0 );
		prompt_width=0;
	}
	
    size_t i;
	for( i=0; commandline[i]; i++ )
	{
		int col = c[i];
		
		if( i == cursor_pos )
		{
			col = 0;
		}
		
		if( i == cursor_pos )
		{
            cursor_arr = s->desired.cursor;
		}
		
		s_desired_append_char( s, commandline[i], col, indent[i], prompt_width );
		
	}
	if( i == cursor_pos )
	{
        cursor_arr = s->desired.cursor;
	}
	
    s->desired.cursor = cursor_arr;
	s_update( s, prompt );
	s_save_status( s );
}
Пример #5
0
/**
   Update the screen to match the desired output.
*/
static void s_update( screen_t *scr, const wchar_t *prompt )
{
	size_t prompt_width = calc_prompt_width( prompt );
	int screen_width = common_get_width();
	int need_clear = scr->need_clear;
	data_buffer_t output;

	scr->need_clear = 0;
		
	if( scr->actual_width != screen_width )
	{
		need_clear = 1;
		s_move( scr, &output, 0, 0 );
		scr->actual_width = screen_width;
		s_reset( scr, false );
	}
	
	if( wcscmp( prompt, scr->actual_prompt.c_str() ) )
	{
		s_move( scr, &output, 0, 0 );
		s_write_str( &output, prompt );
        scr->actual_prompt = prompt;
		scr->actual.cursor.x = (int)prompt_width;
	}
	
    for (size_t i=0; i < scr->desired.line_count(); i++)
	{
		const line_t &o_line = scr->desired.line(i);
		line_t &s_line = scr->actual.create_line(i);
		size_t start_pos = (i==0 ? prompt_width : 0);
        int current_width = 0;
        
		if( need_clear )
		{
			s_move( scr, &output, (int)start_pos, (int)i );
			s_write_mbs( &output, clr_eol);
            s_line.clear();
		}
        
        /* Note that skip_remaining is a width, not a character count */
        size_t skip_remaining = start_pos;
        
        /* Compute how much we should skip. At a minimum we skip over the prompt. But also skip over the shared prefix of what we want to output now, and what we output before, to avoid repeatedly outputting it. */
        size_t shared_prefix = line_shared_prefix(o_line, s_line);
        if (shared_prefix > 0)
        {
            int prefix_width = fish_wcswidth(&o_line.text.at(0), shared_prefix);
            if (prefix_width > skip_remaining)
                skip_remaining = prefix_width;
        }
        
        /* Skip over skip_remaining width worth of characters */
        size_t j = 0;
        for ( ; j < o_line.size(); j++)
        {
            int width = fish_wcwidth(o_line.char_at(j));
            if (skip_remaining <= width)
                break;
            skip_remaining -= width;
            current_width += width;
        }
        
        /* Skip over zero-width characters (e.g. combining marks at the end of the prompt) */
        for ( ; j < o_line.size(); j++)
        {
            int width = fish_wcwidth(o_line.char_at(j));
            if (width > 0)
                break;
        }
        
        /* Now actually output stuff */
        for ( ; j < o_line.size(); j++)
        {
            s_move( scr, &output, current_width, (int)i );
            s_set_color( scr, &output, o_line.color_at(j) );
            s_write_char( scr, &output, o_line.char_at(j) );
            current_width += fish_wcwidth(o_line.char_at(j));
        }
        
        /* If we wrote more on this line last time, clear it */
        int prev_length = (s_line.text.empty() ? 0 : fish_wcswidth(&s_line.text.at(0), s_line.text.size()));
        if (prev_length > current_width )
		{
			s_move( scr, &output, current_width, (int)i );
			s_write_mbs( &output, clr_eol);
		}
	}
    
    /* Clear remaining lines */
    for( size_t i=scr->desired.line_count(); i < scr->actual.line_count(); i++ )
	{
		s_move( scr, &output, 0, (int)i );
		s_write_mbs( &output, clr_eol);
	}
	
	s_move( scr, &output, scr->desired.cursor.x, scr->desired.cursor.y );
	s_set_color( scr, &output, 0xffffffff);

	if( ! output.empty() )
	{
		write_loop( 1, &output.at(0), output.size() );
	}
	
    /* We have now synced our actual screen against our desired screen. Note that this is a big assignment! */
    scr->actual = scr->desired;
}
Пример #6
0
/**
   Convert a wide character to a multibyte string and append it to the
   buffer.
*/
static void s_write_char( screen_t *s, data_buffer_t *b, wchar_t c )
{
	scoped_buffer_t scoped_buffer(b);
	s->actual.cursor.x+=fish_wcwidth( c );
	writech( c );
}
Пример #7
0
/**
   Appends a character to the end of the line that the output cursor is
   on. This function automatically handles linebreaks and lines longer
   than the screen width. 
*/
static void s_desired_append_char( screen_t *s,
                                  wchar_t b,
                                  int c,
                                  int indent,
                                  size_t prompt_width )
{
	int line_no = s->desired.cursor.y;
	
	switch( b )
	{
		case L'\n':
		{
			int i;
            s->desired.create_line(s->desired.line_count());
			s->desired.cursor.y++;
			s->desired.cursor.x=0;
			for( i=0; i < prompt_width+indent*INDENT_STEP; i++ )
			{
				s_desired_append_char( s, L' ', 0, indent, prompt_width );
			}
			break;
		}
            
		case L'\r':
		{
            line_t &current = s->desired.line(line_no);
            current.clear();
            s->desired.cursor.x = 0;
			break;
		}
            
		default:
		{
			int screen_width = common_get_width();
			int cw = fish_wcwidth(b);
			
            s->desired.create_line(line_no);
            
			/*
             Check if we are at the end of the line. If so, continue on the next line.
             */
			if( (s->desired.cursor.x + cw) > screen_width )
			{
                line_no = (int)s->desired.line_count();
                s->desired.add_line();
				s->desired.cursor.y++;
				s->desired.cursor.x=0;
                for( size_t i=0; i < prompt_width; i++ )
                {
                    s_desired_append_char( s, L' ', 0, indent, prompt_width );
                }
			}
			
            line_t &line = s->desired.line(line_no);
            line.append(b, c);
			s->desired.cursor.x+= cw;
			break;
		}
	}
	
}
Пример #8
0
/**
   Calculate the width of the specified prompt. Does some clever magic
   to detect common escape sequences that may be embeded in a prompt,
   such as color codes.
*/
static size_t calc_prompt_width( const wchar_t *prompt )
{
	size_t res = 0;
	size_t j, k;

	for( j=0; prompt[j]; j++ )
	{
		if( prompt[j] == L'\x1b' )
		{
			/*
			  This is the start of an escape code. Try to guess it's width.
			*/
			size_t p;
			int len=0;
			bool found = false;
			
			/*
			  Detect these terminfo color escapes with parameter
			  value 0..7, all of which don't move the cursor
			*/
			char * const esc[] =
				{
					set_a_foreground,
					set_a_background,
					set_foreground,
					set_background,
				}
			;

			/*
			  Detect these semi-common terminfo escapes without any
			  parameter values, all of which don't move the cursor
			*/
			char * const esc2[] =
				{
					enter_bold_mode,
					exit_attribute_mode,
					enter_underline_mode,
					exit_underline_mode,
					enter_standout_mode,
					exit_standout_mode,
					flash_screen,
					enter_subscript_mode,
					exit_subscript_mode,
					enter_superscript_mode,
					exit_superscript_mode,
					enter_blink_mode,
					enter_italics_mode,
					exit_italics_mode,
					enter_reverse_mode,
					enter_shadow_mode,
					exit_shadow_mode,
					enter_standout_mode,
					exit_standout_mode,
					enter_secure_mode
				}
			;

			for( p=0; p < sizeof esc / sizeof *esc && !found; p++ )
			{
				if( !esc[p] )
					continue;

				for( k=0; k<8; k++ )
				{
					len = try_sequence( tparm(esc[p],k), &prompt[j] );
					if( len )
					{
						j += (len-1);
						found = true;
						break;
					}
				}
			}
            
            // PCA for term256 support, let's just detect the escape codes directly
            if (! found) {
                len = is_term256_escape(&prompt[j]);
                if (len) {
                    j += (len - 1);
                    found = true;
                }
            }


			for( p=0; p < (sizeof(esc2)/sizeof(char *)) && !found; p++ )
			{
				if( !esc2[p] )
					continue;
				/*
				  Test both padded and unpadded version, just to
				  be safe. Most versions of tparm don't actually
				  seem to do anything these days.
				*/
				len = maxi( try_sequence( tparm(esc2[p]), &prompt[j] ),
					    try_sequence( esc2[p], &prompt[j] ));
					
				if( len )
				{
					j += (len-1);
					found = true;
				}
			}
				
			if( !found )
			{
				if( prompt[j+1] == L'k' )
				{
					const env_var_t term_name = env_get_string( L"TERM" );
					if( !term_name.missing() && wcsstr( term_name.c_str(), L"screen" ) == term_name )
					{
						const wchar_t *end;
						j+=2;
						found = true;
						end = wcsstr( &prompt[j], L"\x1b\\" );
						if( end )
						{
							/*
							  You'd thing this should be
							  '(end-prompt)+2', in order to move j
							  past the end of the string, but there is
							  a 'j++' at the end of each lap, so j
							  should always point to the last menged
							  character, e.g. +1.
							*/
							j = (end-prompt)+1;
						}
						else
						{
							break;
						}
					}						
				}					
			}
				
		}
		else if( prompt[j] == L'\t' )
		{
			res = next_tab_stop( res );
		}
		else if( prompt[j] == L'\n' )
		{
			res = 0;
		}
		else
		{
			/*
			  Ordinary decent character. Just add width.
			*/
			res += fish_wcwidth( prompt[j] );
		}
	}
	return res;
}
Пример #9
0
/* Like fish_wcwidth, but returns 0 for control characters instead of -1 */
static int fish_wcwidth_min_0(wchar_t wc)
{
    return maxi(0, fish_wcwidth(wc));
}