aboutsummaryrefslogtreecommitdiffhomepage
path: root/reader.c
diff options
context:
space:
mode:
Diffstat (limited to 'reader.c')
-rw-r--r--reader.c3023
1 files changed, 3023 insertions, 0 deletions
diff --git a/reader.c b/reader.c
new file mode 100644
index 00000000..54c29f19
--- /dev/null
+++ b/reader.c
@@ -0,0 +1,3023 @@
+/** \file reader.c
+
+Functions for reading data from stdin and passing to the
+parser. If stdin is a keyboard, it supplies a killring, history,
+syntax highlighting, tab-completion and various other interactive features.
+
+Internally the interactive mode functions rely in the functions of the
+input library to read individual characters of input.
+
+
+Token search is handled incrementally. Actual searches are only done
+on when searching backwards, since the previous results are saved. The
+last search position is remembered and a new search continues from the
+last search position. All search results are saved in the list
+'search_prev'. When the user searches forward, i.e. presses Alt-down,
+the list is consulted for previous search result, and subsequent
+backwards searches are also handled by consultiung the list up until
+the end of the list is reached, at which point regular searching will
+commence.
+
+*/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <wctype.h>
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <time.h>
+#include <wchar.h>
+
+#include "util.h"
+#include "wutil.h"
+#include "highlight.h"
+#include "reader.h"
+#include "proc.h"
+#include "parser.h"
+#include "complete.h"
+#include "history.h"
+#include "common.h"
+#include "sanity.h"
+#include "env.h"
+#include "exec.h"
+#include "expand.h"
+#include "tokenizer.h"
+#include "kill.h"
+#include "input_common.h"
+#include "input.h"
+#include "function.h"
+#include "output.h"
+
+/**
+ Maximum length of prefix string when printing completion
+ list. Longer prefixes will be ellipsized.
+*/
+#define PREFIX_MAX_LEN 8
+
+/**
+ A simple prompt for reading shell commands that does not rely on
+ fish specific commands, meaning it will work even if fish is not
+ installed. This is used by read_i.
+*/
+#define DEFAULT_PROMPT L"whoami; echo @; hostname|cut -d . -f 1; echo \" \"; pwd; printf '> ';"
+
+/**
+ The default title for the reader. This is used by reader_readline.
+*/
+#define DEFAULT_TITLE L"echo $_ \" \"; pwd"
+
+/**
+ A struct describing the state of the interactive reader. These
+ states can be stacked, in case reader_readline is called from
+ input_read().
+*/
+typedef struct reader_data
+{
+ /**
+ Buffer containing the current commandline
+ */
+ wchar_t *buff;
+
+ /**
+ The output string, may be different than buff if buff can't fit on one line.
+ */
+ wchar_t *output;
+
+ /**
+ The number of characters used by the prompt
+ */
+ int prompt_width;
+
+ /**
+ Buffer containing the current search item
+ */
+ wchar_t *search_buff;
+ /**
+ Saved position used by token history search
+ */
+ int token_history_pos;
+
+ /**
+ Saved search string for token history search. Not handled by check_size.
+ */
+ const wchar_t *token_history_buff;
+
+ /**
+ List for storing previous search results. Used to avoid duplicates.
+ */
+ array_list_t search_prev;
+
+ /**
+ The current position in search_prev
+ */
+
+ int search_pos;
+
+ /**
+ Current size of the buffers
+ */
+ size_t buff_sz;
+
+ /**
+ Length of the command in buff. (Not the length of buff itself)
+ */
+
+ size_t buff_len;
+
+ /**
+ The current position of the cursor in buff.
+ */
+ size_t buff_pos;
+
+ /**
+ The current position of the cursor in output buffer.
+ */
+ size_t output_pos;
+
+ /**
+ Name of the current application
+ */
+ wchar_t *name;
+
+ /** The prompt text */
+ wchar_t *prompt;
+
+ /**
+ Color is the syntax highlighting for buff. The format is that
+ color[i] is the classification (according to the enum in
+ highlight.h) of buff[i].
+ */
+ int *color;
+
+ /**
+ New color buffer, used for syntax highlighting.
+ */
+ int *new_color;
+
+ /**
+ Color for the actual output string.
+ */
+ int *output_color;
+
+ /**
+ Should the prompt command be reexecuted on the next repaint
+ */
+ int exec_prompt;
+
+ /**
+ Function for tab completion
+ */
+ void (*complete_func)( const wchar_t *,
+ array_list_t * );
+
+ /**
+ Function for syntax highlighting
+ */
+ void (*highlight_func)( wchar_t *,
+ int *,
+ int,
+ array_list_t * );
+
+ /**
+ Function for testing if the string can be returned
+ */
+ int (*test_func)( wchar_t * );
+
+ /**
+ When this is true, the reader will exit
+ */
+ int end_loop;
+
+ /**
+ Pointer to previous reader_data
+ */
+ struct reader_data *next;
+}
+ reader_data_t;
+
+/**
+ The current interactive reading context
+*/
+static reader_data_t *data=0;
+
+
+/**
+ Flag for ending non-interactive shell
+*/
+static int end_loop = 0;
+
+
+/**
+ This struct should be continually updated by signals as the term resizes, and as such always contain the correct current size.
+*/
+static struct winsize termsize;
+
+/**
+ This flag is set when a WINCH signal was recieved.
+*/
+static int new_size=0;
+
+
+/**
+ The list containing names of files that are being parsed
+*/
+static array_list_t current_filename;
+
+/**
+ These status buffers are used to check if any output has occurred
+ other than from fish's main loop, in which case we need to redraw.
+*/
+static struct stat prev_buff_1, prev_buff_2, post_buff_1, post_buff_2;
+
+
+
+/**
+ List containing strings which make up the prompt
+*/
+static array_list_t prompt_list;
+
+/**
+ Stores the previous termios mode so we can reset the modes when
+ we execute programs and when the shell exits.
+*/
+static struct termios saved_modes;
+
+
+/**
+ Store the pid of the parent process, so the exit function knows whether it should reset the terminal or not.
+*/
+static pid_t original_pid;
+
+/**
+ Interrupted flag. Set to 1 when the user presses \^C.
+*/
+static int interupted;
+
+/*
+ Prototypes for a bunch of functions defined later on.
+*/
+
+static void reader_save_status();
+static void reader_check_status();
+static void reader_super_highlight_me_plenty( wchar_t * buff, int *color, int pos, array_list_t *error );
+static void handle_winch( int sig );
+
+
+
+
+static struct termios old_modes;
+
+static void term_donate()
+{
+ tcgetattr(0,&old_modes); /* get the current terminal modes */
+
+ set_color(FISH_COLOR_NORMAL, FISH_COLOR_NORMAL);
+
+ while( 1 )
+ {
+ if( tcsetattr(0,TCSANOW,&saved_modes) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not set terminal mode for new job" );
+ wperror( L"tcsetattr" );
+ break;
+ }
+ }
+ else
+ break;
+ }
+
+
+}
+
+static void term_steal()
+{
+
+ while( 1 )
+ {
+ if( tcsetattr(0,TCSANOW,&shell_modes) )
+ {
+ if( errno != EINTR )
+ {
+ debug( 1, L"Could not set terminal mode for shell" );
+ wperror( L"tcsetattr" );
+ break;
+ }
+ }
+ else
+ break;
+ }
+
+ handle_winch( 0 );
+
+ if( tcsetattr(0,TCSANOW,&old_modes)) /* return to previous mode */
+ {
+ wperror(L"tcsetattr");
+ exit(1);
+ }
+
+}
+
+/**
+ Test if there is space between the time fields of struct stat to
+ use for sub second information. If so, we assume this space
+ contains the desired information.
+*/
+static int room_for_usec(struct stat *st)
+{
+ int res = ((&(st->st_atime) + 2) == &(st->st_mtime) &&
+ (&(st->st_atime) + 4) == &(st->st_ctime));
+ return res;
+}
+
+/**
+ string_buffer used as temporary storage for the reader_readline function
+*/
+static string_buffer_t *readline_buffer=0;
+
+int reader_get_width()
+{
+ return termsize.ws_col;
+}
+
+
+int reader_get_height()
+{
+ return termsize.ws_row;
+}
+
+
+wchar_t *reader_current_filename()
+{
+ return (wchar_t *)al_peek( &current_filename );
+}
+
+
+void reader_push_current_filename( wchar_t *fn )
+{
+ al_push( &current_filename, fn );
+}
+
+
+wchar_t *reader_pop_current_filename()
+{
+ return (wchar_t *)al_pop( &current_filename );
+}
+
+
+/**
+ Make sure buffers are large enough to hold current data plus one extra character.
+*/
+static int check_size()
+{
+ if( data->buff_sz < data->buff_len + 2 )
+ {
+ data->buff_sz = maxi( 128, data->buff_len*2 );
+
+ data->buff = realloc( data->buff,
+ sizeof(wchar_t)*data->buff_sz);
+ data->search_buff = realloc( data->search_buff,
+ sizeof(wchar_t)*data->buff_sz);
+ data->output = realloc( data->output,
+ sizeof(wchar_t)*data->buff_sz);
+
+ data->color = realloc( data->color,
+ sizeof(int)*data->buff_sz);
+ data->new_color = realloc( data->new_color,
+ sizeof(int)*data->buff_sz);
+ data->output_color = realloc( data->output_color,
+ sizeof(int)*data->buff_sz);
+
+ if( data->buff==0 ||
+ data->search_buff==0 ||
+ data->color==0 ||
+ data->new_color == 0 )
+ {
+ die_mem();
+
+ }
+ }
+ return 1;
+}
+
+/**
+ Check if the screen is not wide enough for the buffer, which means
+ the buffer must be scrolled on input and cursor movement.
+*/
+static int force_repaint()
+{
+ int max_width = reader_get_width() - data->prompt_width;
+ int pref_width = my_wcswidth( data->buff ) + (data->buff_pos==data->buff_len);
+ return pref_width >= max_width;
+}
+
+
+/**
+ Calculate what part of the buffer should be visible
+
+ \return returns 1 screen needs repainting, 0 otherwise
+*/
+static int calc_output()
+{
+ int max_width = reader_get_width() - data->prompt_width;
+ int pref_width = my_wcswidth( data->buff ) + (data->buff_pos==data->buff_len);
+ if( pref_width <= max_width )
+ {
+ wcscpy( data->output, data->buff );
+ memcpy( data->output_color, data->color, sizeof(int) * data->buff_len );
+ data->output_pos=data->buff_pos;
+
+ return 1;
+ }
+ else
+ {
+ int offset = data->buff_pos;
+ int offset_end = data->buff_pos;
+ int w = 0;
+ wchar_t *pos=data->output;
+ *pos=0;
+
+
+ w = (data->buff_pos==data->buff_len)?1:wcwidth( data->buff[offset] );
+ while( 1 )
+ {
+ int inc=0;
+ int ellipsis_width;
+
+ ellipsis_width = wcwidth(ellipsis_char)*((offset?1:0)+(offset_end<data->buff_len?1:0));
+
+ if( offset > 0 && (ellipsis_width + w + wcwidth( data->buff[offset-1] ) <= max_width ) )
+ {
+ inc=1;
+ offset--;
+ w+= wcwidth( data->buff[offset]);
+ }
+
+ ellipsis_width = wcwidth(ellipsis_char)*((offset?1:0)+(offset_end<data->buff_len?1:0));
+
+ if( offset_end < data->buff_len && (ellipsis_width + w + wcwidth( data->buff[offset_end+1] ) <= max_width ) )
+ {
+ inc = 1;
+ offset_end++;
+ w+= wcwidth( data->buff[offset_end]);
+ }
+
+ if( !inc )
+ break;
+
+ }
+
+ data->output_pos = data->buff_pos - offset + (offset?1:0);
+
+ if( offset )
+ {
+ data->output[0]=ellipsis_char;
+ data->output[1]=0;
+
+ }
+
+ wcsncat( data->output,
+ data->buff+offset,
+ offset_end-offset );
+
+ if( offset_end<data->buff_len )
+ {
+ int l = wcslen(data->output);
+
+ data->output[l]=ellipsis_char;
+ data->output[l+1]=0;
+
+ }
+
+ *data->output_color=HIGHLIGHT_NORMAL;
+
+ memcpy( data->output_color+(offset?1:0),
+ data->color+offset,
+ sizeof(int) * (data->buff_len-offset) );
+ return 1;
+ }
+}
+
+
+/**
+ Compare two completions, ignoring their description.
+*/
+static int fldcmp( wchar_t *a, wchar_t *b )
+{
+ while( 1 )
+ {
+ if( *a != *b )
+ return *a-*b;
+ if( ( (*a == COMPLETE_SEP) || (*a == L'\0') ) &&
+ ( (*b == COMPLETE_SEP) || (*b == L'\0') ) )
+ return 0;
+ a++;
+ b++;
+ }
+
+}
+
+/**
+ Remove any duplicate completions in the list. This relies on the
+ list first beeing sorted.
+*/
+static void remove_duplicates( array_list_t *l )
+{
+ int in, out;
+ wchar_t *prev;
+ if( al_get_count( l ) == 0 )
+ return;
+
+ prev = (wchar_t *)al_get( l, 0 );
+ for( in=1, out=1; in < al_get_count( l ); in++ )
+ {
+ wchar_t *curr = (wchar_t *)al_get( l, in );
+ if( fldcmp( prev, curr )==0 )
+ {
+ free( curr );
+ }
+ else
+ {
+ al_set( l, out++, curr );
+ prev = curr;
+ }
+ }
+ al_truncate( l, out );
+}
+
+
+/**
+ Translate a highlighting code ()Such as as returned by the highlight function
+ into a color code which is then passed on to set_color.
+*/
+static void set_color_translated( int c )
+{
+ set_color( highlight_get_color( c & 0xff ),
+ highlight_get_color( (c>>8)&0xff ) );
+}
+
+int reader_interupted()
+{
+ int res=interupted;
+ if( res )
+ interupted=0;
+ return res;
+}
+
+void reader_write_title()
+{
+ wchar_t *title;
+ array_list_t l;
+ wchar_t *term = env_get( L"TERM" );
+
+ /*
+ This is a pretty lame heuristic for detecting terminals that do
+ not support setting the title. If we recognise the terminal name
+ as that of a virtual terminal, we assume it supports setting the
+ title. Otherwise we check the ttyname.
+ */
+ if( !term || !contains_str( term, L"xterm", L"screen", L"nxterm", L"rxvt", 0 ) )
+ {
+ char *n = ttyname( STDIN_FILENO );
+ if( strstr( n, "tty" ) || strstr( n, "/vc/") )
+ return;
+ }
+
+ title = function_exists( L"fish_title" )?L"fish_title":DEFAULT_TITLE;
+
+ if( wcslen( title ) ==0 )
+ return;
+
+ al_init( &l );
+
+ if( exec_subshell( title, &l ) != -1 )
+ {
+ int i;
+ writestr( L"\e]2;" );
+ for( i=0; i<al_get_count( &l ); i++ )
+ {
+ writestr( (wchar_t *)al_get( &l, i ) );
+ }
+ writestr( L"\7" );
+ }
+ al_foreach( &l, (void (*)(const void *))&free );
+ al_destroy( &l );
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+}
+
+/**
+ Write the prompt to screen. If data->exec_prompt is set, the prompt
+ command is first evaluated, and the title will be reexecuted as
+ well.
+*/
+static void write_prompt()
+{
+ int i;
+ set_color( FISH_COLOR_NORMAL, FISH_COLOR_NORMAL );
+
+ /*
+ Check if we need to reexecute the prompt command
+ */
+ if( data->exec_prompt )
+ {
+
+ al_foreach( &prompt_list, (void (*)(const void *))&free );
+ al_truncate( &prompt_list, 0 );
+
+ if( data->prompt )
+ {
+ if( exec_subshell( data->prompt, &prompt_list ) == -1 )
+ {
+ /* If executing the prompt fails, make sure we at least don't print any junk */
+ al_foreach( &prompt_list, (void (*)(const void *))&free );
+ al_destroy( &prompt_list );
+ al_init( &prompt_list );
+ }
+ }
+ data->prompt_width=0;
+ for( i=0; i<al_get_count( &prompt_list ); i++ )
+ {
+ wchar_t *next = (wchar_t *)al_get( &prompt_list, i );
+ if( *next == L'\e' )
+ continue;
+ data->prompt_width += my_wcswidth( next );
+ }
+
+ data->exec_prompt = 0;
+ reader_write_title();
+ }
+
+ /*
+ Write out the prompt strings
+ */
+
+ for( i=0; i<al_get_count( &prompt_list); i++ )
+ {
+ writestr( (wchar_t *)al_get( &prompt_list, i ) );
+ }
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+
+}
+
+/**
+ Write the whole command line (but not the prompt) to the screen. Do
+ not set the cursor correctly afterwards.
+*/
+static void write_cmdline()
+{
+ int i;
+
+ for( i=0; data->output[i]; i++ )
+ {
+ set_color_translated( data->output_color[i] );
+ writech( data->output[i] );
+ }
+}
+
+/**
+ perm_left_cursor and parm_right_cursor don't seem to be defined as
+ often as cursor_left and cursor_right, so we use this workalike.
+*/
+static void move_cursor( int steps )
+{
+ int i;
+
+ if( steps < 0 ){
+ for( i=0; i>steps; i--)
+ {
+ writembs(cursor_left);
+ }
+ }
+ else
+ for( i=0; i<steps; i++)
+ writembs(cursor_right);
+}
+
+
+void reader_init()
+{
+ al_init( &current_filename);
+}
+
+
+void reader_destroy()
+{
+ al_destroy( &current_filename);
+ if( readline_buffer )
+ {
+ sb_destroy( readline_buffer );
+ free( readline_buffer );
+ readline_buffer=0;
+ }
+}
+
+
+void reader_exit( int do_exit )
+{
+ if( is_interactive )
+ data->end_loop=do_exit;
+ else
+ end_loop=do_exit;
+}
+
+void repaint()
+{
+ int steps;
+
+ calc_output();
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+ writech('\r');
+ writembs(clr_eol);
+ write_prompt();
+ write_cmdline();
+
+/*
+ fwprintf( stderr, L"Width of \'%ls\' (length is %d): ",
+ &data->buff[data->buff_pos],
+ wcslen(&data->buff[data->buff_pos]));
+ fwprintf( stderr, L"%d\n", my_wcswidth(&data->buff[data->buff_pos]));
+*/
+
+ steps = my_wcswidth( &data->output[data->output_pos]);
+ if( steps )
+ move_cursor( -steps );
+
+ set_color( FISH_COLOR_NORMAL, -1 );
+ reader_save_status();
+}
+
+/**
+ Make sure color values are correct, and repaint if they are not.
+*/
+static void check_colors()
+{
+ reader_super_highlight_me_plenty( data->buff, data->new_color, data->buff_pos, 0 );
+ if( memcmp( data->new_color, data->color, sizeof(int)*data->buff_len )!=0 )
+ {
+ memcpy( data->color, data->new_color, sizeof(int)*data->buff_len );
+
+ repaint();
+ }
+}
+
+/**
+ Stat stdout and stderr and save result.
+
+ This should be done before calling a function that may cause output.
+*/
+
+static void reader_save_status()
+{
+
+#if (defined(__FreeBSD__) || defined(__NetBSD__))
+ /*
+ This futimes call tries to trick the system into using st_mtime
+ as a tampering flag. This of course only works on systems where
+ futimes is defined, but it should make the status saving stuff
+ failsafe.
+ */
+ struct timeval t=
+ {
+ time(0)-1,
+ 0
+ }
+ ;
+
+ if( futimes( 1, &t ) || futimes( 2, &t ) )
+ {
+ wperror( L"futimes" );
+ }
+#endif
+
+ fstat( 1, &prev_buff_1 );
+ fstat( 2, &prev_buff_2 );
+}
+
+/**
+ Stat stdout and stderr and compare result to previous result in
+ reader_save_status. Repaint if modification time has changed.
+
+ Unfortunately, for some reason this call seems to give a lot of
+ false positives, at least under Linux.
+*/
+
+static void reader_check_status()
+{
+ fflush( stdout );
+ fflush( stderr );
+
+ fstat( 1, &post_buff_1 );
+ fstat( 2, &post_buff_2 );
+
+ int changed = ( prev_buff_1.st_mtime != post_buff_1.st_mtime ) ||
+ ( prev_buff_2.st_mtime != post_buff_2.st_mtime );
+
+ if (room_for_usec( &post_buff_1))
+ {
+ changed = changed || ( (&prev_buff_1.st_mtime)[1] != (&post_buff_1.st_mtime)[1] ) ||
+ ( (&prev_buff_2.st_mtime)[1] != (&post_buff_2.st_mtime)[1] );
+ }
+
+ if( changed )
+ {
+ repaint();
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+ }
+}
+
+/**
+ Remove the previous character in the character buffer and on the
+ screen using syntax highlighting, etc.
+*/
+static void remove_backward()
+{
+ int wdt;
+
+ if( data->buff_pos <= 0 )
+ return;
+
+ if( data->buff_pos < data->buff_len )
+ {
+ memmove( &data->buff[data->buff_pos-1],
+ &data->buff[data->buff_pos],
+ sizeof(wchar_t)*(data->buff_len-data->buff_pos+1) );
+
+ memmove( &data->color[data->buff_pos-1],
+ &data->color[data->buff_pos],
+ sizeof(wchar_t)*(data->buff_len-data->buff_pos+1) );
+ }
+ data->buff_pos--;
+ data->buff_len--;
+
+ wdt=wcwidth(data->buff[data->buff_pos]);
+ move_cursor(-wdt);
+ data->buff[data->buff_len]='\0';
+// wcscpy(data->search_buff,data->buff);
+
+ reader_super_highlight_me_plenty( data->buff,
+ data->new_color,
+ data->buff_pos,
+ 0 );
+ if( (!force_repaint()) && ( memcmp( data->new_color,
+ data->color,
+ sizeof(int)*data->buff_len )==0 ) &&
+ ( delete_character != 0) && (wdt==1) )
+ {
+ /*
+ Only do this if delete mode functions, and only for a column
+ wide characters, since terminfo seems to break for other
+ characters. This last check should be removed when terminfo
+ is fixed.
+ */
+ if( enter_delete_mode != 0 )
+ writembs(enter_delete_mode);
+ writembs(delete_character);
+ if( exit_delete_mode != 0 )
+ writembs(exit_delete_mode);
+ }
+ else
+ {
+ memcpy( data->color,
+ data->new_color,
+ sizeof(int) * data->buff_len );
+
+ repaint();
+ }
+}
+
+/**
+ Remove the current character in the character buffer and on the
+ screen using syntax highlighting, etc.
+*/
+static void remove_forward()
+{
+ if( data->buff_pos >= data->buff_len )
+ return;
+
+ move_cursor(wcwidth(data->buff[data->buff_pos]));
+ data->buff_pos++;
+
+ remove_backward();
+}
+
+/**
+ Insert the character into the command line buffer and print it to
+ the screen using syntax highlighting, etc.
+*/
+static int insert_char( int c )
+{
+
+ if( !check_size() )
+ return 0;
+
+ /* Insert space for extra character at the right position */
+ if( data->buff_pos < data->buff_len )
+ {
+ memmove( &data->buff[data->buff_pos+1],
+ &data->buff[data->buff_pos],
+ sizeof(wchar_t)*(data->buff_len-data->buff_pos) );
+
+ memmove( &data->color[data->buff_pos+1],
+ &data->color[data->buff_pos],
+ sizeof(int)*(data->buff_len-data->buff_pos) );
+ }
+ /* Set character */
+ data->buff[data->buff_pos]=c;
+
+ /* Update lengths, etc */
+ data->buff_pos++;
+ data->buff_len++;
+ data->buff[data->buff_len]='\0';
+
+ /* Syntax highlight */
+
+ reader_super_highlight_me_plenty( data->buff,
+ data->new_color,
+ data->buff_pos-1,
+ 0 );
+ data->color[data->buff_pos-1] = data->new_color[data->buff_pos-1];
+
+ /* Check if the coloring has changed */
+ if( (!force_repaint()) && ( memcmp( data->new_color,
+ data->color,
+ sizeof(int)*data->buff_len )==0 ) &&
+ ( insert_character ||
+ ( data->buff_pos == data->buff_len ) ||
+ enter_insert_mode) )
+ {
+ /*
+ Colors look ok, so we set the right color and insert a
+ character
+ */
+ set_color_translated( data->color[data->buff_pos-1] );
+ if( data->buff_pos < data->buff_len )
+ {
+ if( enter_insert_mode != 0 )
+ writembs(enter_insert_mode);
+ else
+ writembs(insert_character);
+ writech(c);
+ if( insert_padding != 0 )
+ writembs(insert_padding);
+ if( exit_insert_mode != 0 )
+ writembs(exit_insert_mode);
+ }
+ else
+ writech(c);
+ set_color( FISH_COLOR_NORMAL, -1 );
+ }
+ else
+ {
+ /* Nope, colors are off, so we repaint the entire command line */
+ memcpy( data->color, data->new_color, sizeof(int) * data->buff_len );
+
+ repaint();
+ }
+// wcscpy(data->search_buff,data->buff);
+ return 1;
+}
+
+/**
+ Insert the characters of the string into the command line buffer
+ and print them to the screen using syntax highlighting, etc.
+*/
+static int insert_str(wchar_t *str)
+{
+ while( (*str)!=0 )
+ if(!insert_char( *str++ ))
+ return 0;
+ return 1;
+}
+
+/**
+ Calculate the length of the common prefix substring of two strings.
+*/
+static int comp_len( wchar_t *a, wchar_t *b )
+{
+ int i;
+ for( i=0;
+ a[i] != '\0' && b[i] != '\0' && a[i]==b[i];
+ i++ )
+ ;
+ return i;
+}
+
+/**
+ Find the outermost quoting style of current token. Returns 0 if token is not quoted.
+
+*/
+static wchar_t get_quote( wchar_t *cmd, int l )
+{
+ int i=0;
+ wchar_t res=0;
+
+// fwprintf( stderr, L"Woot %ls\n", cmd );
+
+ while( 1 )
+ {
+ if( !cmd[i] )
+ break;
+
+ if( cmd[i] == L'\'' || cmd[i] == L'\"' )
+ {
+ wchar_t *end = quote_end( &cmd[i] );
+ //fwprintf( stderr, L"Jump %d\n", end-cmd );
+ if(( end == 0 ) || (!*end) || (end-cmd > l))
+ {
+ res = cmd[i];
+ break;
+ }
+ i = end-cmd+1;
+ }
+ else
+ i++;
+
+ }
+ return res;
+}
+
+/**
+ Calculates information on the parameter at the specified index.
+
+ \param cmd The command to be analyzed
+ \param pos An index in the string which is inside the parameter
+ \param quote If not 0, store the type of quote this parameter has, can be either ', " or \\0, meaning the string is not quoted.
+ \param offset If not 0, get_param will store a pointer to the beginning of the parameter.
+ \param string If not o, get_parm will store a copy of the parameter string as returned by the tokenizer.
+ \param type If not 0, get_param will store the token type as returned by tok_last.
+*/
+static void get_param( wchar_t *cmd,
+ int pos,
+ wchar_t *quote,
+ wchar_t **offset,
+ wchar_t **string,
+ int *type )
+{
+ int prev_pos=0;
+ wchar_t last_quote = '\0';
+ int unfinished;
+
+ tokenizer tok;
+ tok_init( &tok, cmd, TOK_ACCEPT_UNFINISHED );
+
+ for( ; tok_has_next( &tok ); tok_next( &tok ) )
+ {
+ if( tok_get_pos( &tok ) > pos )
+ break;
+
+ if( tok_last_type( &tok ) == TOK_STRING )
+ last_quote = get_quote( tok_last( &tok ),
+ pos - tok_get_pos( &tok ) );
+
+ if( type != 0 )
+ *type = tok_last_type( &tok );
+ if( string != 0 )
+ wcscpy( *string, tok_last( &tok ) );
+ prev_pos = tok_get_pos( &tok );
+ }
+
+ tok_destroy( &tok );
+
+ wchar_t c = cmd[pos];
+ cmd[pos]=0;
+ int cmdlen = wcslen( cmd );
+ unfinished = (cmdlen==0);
+ if( !unfinished )
+ {
+ unfinished = (quote != 0);
+
+ if( !unfinished )
+ {
+ if( wcschr( L" \t\n\r", cmd[cmdlen-1] ) != 0 )
+ {
+ if( ( cmdlen == 1) || (cmd[cmdlen-2] != L'\\') )
+ {
+ unfinished=1;
+ }
+ }
+ }
+ }
+
+ if( quote )
+ *quote = last_quote;
+
+ if( offset != 0 )
+ {
+ if( !unfinished )
+ {
+ while( (cmd[prev_pos] != 0) && (wcschr( L";|",cmd[prev_pos])!= 0) )
+ prev_pos++;
+
+ *offset = cmd + prev_pos;
+ }
+ else
+ {
+ *offset = cmd + pos;
+ }
+ }
+ cmd[pos]=c;
+}
+
+/**
+ Insert the string at the current cursor position. The function
+ checks if the string is quoted or not and correctly escapes the
+ string.
+
+ \param val the string to insert
+ \param is_complete Whether this completion is the whole string or
+ just the common prefix of several completions. If the former, end by
+ printing a space (and an end quote if the parameter is quoted).
+*/
+static void completion_insert( wchar_t *val, int is_complete )
+{
+ wchar_t *replaced;
+
+ wchar_t quote;
+
+ get_param( data->buff,
+ data->buff_pos,
+ &quote,
+ 0, 0, 0 );
+
+ if( quote == L'\0' )
+ {
+ replaced = expand_escape( wcsdup(val), 1 );
+ }
+ else
+ {
+ int unescapable=0;
+
+ wchar_t *pin, *pout;
+
+ replaced = pout =
+ malloc( sizeof(wchar_t)*(wcslen(val) + 1) );
+
+ for( pin=val; *pin; pin++ )
+ {
+ switch( *pin )
+ {
+ case L'\n':
+ case L'\t':
+ case L'\b':
+ case L'\r':
+ unescapable=1;
+ break;
+ default:
+ *pout++ = *pin;
+ break;
+ }
+ }
+ if( unescapable )
+ {
+ free( replaced );
+ wchar_t *tmp = expand_escape( wcsdup(val), 1 );
+ replaced = wcsdupcat( L" ", tmp );
+ free( tmp);
+ replaced[0]=quote;
+ }
+ else
+ *pout = 0;
+ }
+
+ if( insert_str( replaced ) )
+ {
+
+ if( is_complete ) /* Print trailing space since this is the only completion */
+ {
+
+ if( (quote) &&
+ (data->buff[data->buff_pos] != quote ) ) /* This is a quoted parameter, first print a quote */
+ {
+ insert_char( quote );
+ }
+ insert_char( L' ' );
+ }
+ }
+
+ free(replaced);
+}
+
+/**
+ Run the fish_pager command to display the completion list, and
+ insert the result into the backbuffer.
+*/
+
+static void run_pager( wchar_t *prefix, int is_quoted, array_list_t *comp )
+{
+ int i;
+ string_buffer_t cmd;
+ wchar_t * prefix_esc;
+
+ if( !prefix || (wcslen(prefix)==0))
+ prefix_esc = wcsdup(L"\"\"");
+ else
+ prefix_esc = escape( wcsdup(prefix),1);
+
+ sb_init( &cmd );
+ sb_printf( &cmd,
+ L"fish_pager %d %ls",
+ is_quoted,
+ prefix_esc );
+
+ free( prefix_esc );
+
+ for( i=0; i<al_get_count( comp); i++ )
+ {
+ wchar_t *el = escape( wcsdup((wchar_t*)al_get( comp, i )),1);
+ sb_printf( &cmd, L" %ls", el );
+ free(el);
+ }
+
+ term_donate();
+
+ io_data_t *out = exec_make_io_buffer();
+
+ eval( (wchar_t *)cmd.buff, out, TOP);
+ term_steal();
+
+ int nil=0;
+ b_append( out->out_buffer, &nil, 1 );
+
+ wchar_t *tmp;
+ wchar_t *str = str2wcs((char *)out->out_buffer->buff);
+
+ if( str )
+ {
+ for( tmp = str + wcslen(str)-1; tmp >= str; tmp-- )
+ {
+ input_unreadch( *tmp );
+ }
+ free( str );
+ }
+
+ exec_free_io_buffer( out);
+
+}
+
+/**
+ Handle the list of completions. This means the following:
+
+ - If the list is empty, flash the terminal.
+ - If the list contains one element, write the whole element, and if
+ the element does not end on a '/', '@', ':', or a '=', also write a trailing
+ space.
+ - If the list contains multiple elements with a common prefix, write
+ the prefix.
+ - If the list contains multiple elements without
+ a common prefix, call run_pager to display a list of completions
+
+ \param comp the list of completion strings
+*/
+
+
+static int handle_completions( array_list_t *comp )
+{
+ int i;
+
+ if( al_get_count( comp ) == 0 )
+ {
+ if( flash_screen != 0 )
+ writembs( flash_screen );
+ return 0;
+ }
+ else if( al_get_count( comp ) == 1 )
+ {
+ wchar_t *comp_str = wcsdup((wchar_t *)al_get( comp, 0 ));
+ wchar_t *woot = wcschr( comp_str, COMPLETE_SEP );
+ if( woot != 0 )
+ *woot = L'\0';
+ completion_insert( comp_str,
+ ( wcslen(comp_str) == 0 ) ||
+ ( wcschr( L"/=@:",
+ comp_str[wcslen(comp_str)-1] ) == 0 ) );
+ free( comp_str );
+ return 1;
+ }
+ else
+ {
+ wchar_t *base = wcsdup( (wchar_t *)al_get( comp, 0 ) );
+ int len = wcslen( base );
+ for( i=1; i<al_get_count( comp ); i++ )
+ {
+ int new_len = comp_len( base, (wchar_t *)al_get( comp, i ) );
+ len = new_len < len ? new_len: len;
+ }
+ if( len > 0 )
+ {
+ base[len]=L'\0';
+ wchar_t *woot = wcschr( base, COMPLETE_SEP );
+ if( woot != 0 )
+ *woot = L'\0';
+ completion_insert(base, 0);
+ }
+ else
+ {
+ /*
+ There is no common prefix in the completions, and show_list
+ is true, so we print the list
+ */
+ int len;
+ wchar_t * prefix;
+ wchar_t * prefix_start;
+ get_param( data->buff,
+ data->buff_pos,
+ 0,
+ &prefix_start,
+ 0,
+ 0 );
+
+
+ len = &data->buff[data->buff_pos]-prefix_start;
+
+ if( len <= PREFIX_MAX_LEN )
+ {
+ prefix = malloc( sizeof(wchar_t)*(len+1) );
+ wcsncpy( prefix, prefix_start, len );
+ prefix[len]=L'\0';
+ }
+ else
+ {
+ wchar_t tmp[2]=
+ {
+ ellipsis_char,
+ 0
+ }
+ ;
+
+ prefix = wcsdupcat( tmp,
+ prefix_start + (len - PREFIX_MAX_LEN+1) );
+ }
+
+ {
+ int is_quoted;
+
+ wchar_t quote;
+ get_param( data->buff, data->buff_pos, &quote, 0, 0, 0 );
+ is_quoted = (quote != L'\0');
+
+ writech(L'\n');
+
+ run_pager( prefix, is_quoted, comp );
+
+
+ /*
+ Try to print a list of completions. First try with five
+ columns, then four, etc. completion_try_print always
+ succeeds with one column.
+ */
+/*
+*/
+ }
+
+ free( prefix );
+
+ repaint();
+
+ }
+
+ free( base );
+ return len;
+ }
+}
+
+/**
+ Respond to a winch signal by checking the terminal size
+*/
+static void handle_winch( int sig )
+{
+ if (ioctl(1,TIOCGWINSZ,&termsize)!=0)
+ {
+ return;
+ }
+ new_size=1;
+}
+
+
+void check_winch()
+{
+ if( new_size )
+ {
+ wchar_t tmp[64];
+ new_size=0;
+ swprintf(tmp, 64, L"%d", termsize.ws_row );
+ env_set( L"LINES", tmp, ENV_GLOBAL );
+ swprintf(tmp, 64, L"%d", termsize.ws_col );
+ env_set( L"COLUMNS", tmp, ENV_GLOBAL );
+ }
+}
+
+/**
+ Interactive mode ^C handler. Respond to int signal by setting
+ interrupted-flag and stopping all loops and conditionals.
+*/
+static void handle_int( int sig )
+{
+ interupted=1;
+
+ block_t *c = current_block;
+ while( c )
+ {
+ c->skip=1;
+ c=c->outer;
+ }
+
+}
+
+/**
+ Reset the terminal. This function is placed in the list of
+ functions to call when exiting by using the atexit function. It
+ checks whether it is the original parent process that is exiting
+ and not a subshell, and if it is the parent, it restores the
+ terminal.
+*/
+static void exit_func()
+{
+ if( getpid() == original_pid )
+ tcsetattr(0, TCSANOW, &saved_modes);
+}
+
+/**
+ Sets appropriate signal handlers.
+*/
+static void set_signal_handlers()
+{
+ struct sigaction act;
+ sigemptyset( & act.sa_mask );
+ act.sa_flags=0;
+ act.sa_handler=SIG_DFL;
+
+ /*
+ First reset everything
+ */
+ sigaction( SIGINT, &act, 0);
+ sigaction( SIGQUIT, &act, 0);
+ sigaction( SIGTSTP, &act, 0);
+ sigaction( SIGTTIN, &act, 0);
+ sigaction( SIGTTOU, &act, 0);
+ sigaction( SIGCHLD, &act, 0);
+
+ if( is_interactive )
+ {
+
+ /*
+ Interactive mode. Ignore interactive signals. We are a
+ shell, we know whats best for the user. ;-)
+ */
+
+ act.sa_handler=SIG_IGN;
+
+ sigaction( SIGINT, &act, 0);
+ sigaction( SIGQUIT, &act, 0);
+ sigaction( SIGTSTP, &act, 0);
+ sigaction( SIGTTIN, &act, 0);
+ sigaction( SIGTTOU, &act, 0);
+
+ act.sa_handler = &handle_int;
+ act.sa_flags = 0;
+ if( sigaction( SIGINT, &act, 0) )
+ {
+ wperror( L"sigaction" );
+ exit(1);
+ }
+
+ act.sa_sigaction = &job_handle_signal;
+ act.sa_flags = SA_SIGINFO;
+ if( sigaction( SIGCHLD, &act, 0) )
+ {
+ wperror( L"sigaction" );
+ exit(1);
+ }
+
+ act.sa_flags = 0;
+ act.sa_handler= &handle_winch;
+ if( sigaction( SIGWINCH, &act, 0 ) )
+ {
+ wperror( L"sigaction" );
+ exit(1);
+ }
+
+ }
+ else
+ {
+ /*
+ Non-interactive. Ignore interrupt, check exit status of
+ processes to determine result instead.
+ */
+ act.sa_handler=SIG_IGN;
+
+ sigaction( SIGINT, &act, 0);
+ sigaction( SIGQUIT, &act, 0);
+
+ act.sa_handler=SIG_DFL;
+
+ act.sa_sigaction = &job_handle_signal;
+ act.sa_flags = SA_SIGINFO;
+ if( sigaction( SIGCHLD, &act, 0) )
+ {
+ wperror( L"sigaction" );
+ exit(1);
+ }
+ }
+}
+
+
+/**
+ Initialize data for interactive use
+*/
+static void reader_interactive_init()
+{
+ /* See if we are running interactively. */
+ pid_t shell_pgid;
+
+ input_init();
+ kill_init();
+ shell_pgid = getpgrp ();
+
+ /* Loop until we are in the foreground. */
+ while (tcgetpgrp( 0 ) != shell_pgid)
+ {
+ kill (- shell_pgid, SIGTTIN);
+ }
+
+ /* Put ourselves in our own process group. */
+ shell_pgid = getpid ();
+ if( getpgrp() != shell_pgid )
+ {
+ if (setpgid (shell_pgid, shell_pgid) < 0)
+ {
+ debug( 1,
+ L"Couldn't put the shell in its own process group");
+ wperror( L"setpgid" );
+ exit (1);
+ }
+ }
+
+ /* Grab control of the terminal. */
+ if( tcsetpgrp (STDIN_FILENO, shell_pgid) )
+ {
+ debug( 1,
+ L"Couldn't grab control of terminal" );
+ wperror( L"tcsetpgrp" );
+ exit(1);
+ }
+
+
+ al_init( &prompt_list );
+ history_init();
+
+
+ handle_winch( 0 ); /* Set handler for window change events */
+ check_winch();
+
+ tcgetattr(0,&shell_modes); /* get the current terminal modes */
+ memcpy( &saved_modes,
+ &shell_modes,
+ sizeof(saved_modes)); /* save a copy so we can reset the terminal later */
+
+ shell_modes.c_lflag &= ~ICANON; /* turn off canonical mode */
+ shell_modes.c_lflag &= ~ECHO; /* turn off echo mode */
+ shell_modes.c_cc[VMIN]=1;
+ shell_modes.c_cc[VTIME]=0;
+
+ if( tcsetattr(0,TCSANOW,&shell_modes)) /* set the new modes */
+ {
+ wperror(L"tcsetattr");
+ exit(1);
+ }
+
+ /* We need to know the parents pid so we'll know if we are a subshell */
+ original_pid = getpid();
+
+ if( atexit( &exit_func ) )
+ debug( 1, L"Could not set exit function" );
+
+ env_set( L"_", L"fish", ENV_GLOBAL );
+}
+
+/**
+ Destroy data for interactive use
+*/
+static void reader_interactive_destroy()
+{
+ kill_destroy();
+ al_foreach( &prompt_list, (void (*)(const void *))&free );
+ al_destroy( &prompt_list );
+ history_destroy();
+
+ writestr( L"\n" );
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+ input_destroy();
+}
+
+
+void reader_sanity_check()
+{
+ if( is_interactive)
+ {
+ if(!( data->buff_pos <= data->buff_len ))
+ sanity_lose();
+ if(!( data->buff_len == wcslen( data->buff ) ))
+ sanity_lose();
+ }
+}
+
+void reader_current_subshell_extent( wchar_t **a, wchar_t **b )
+{
+ wchar_t *buffcpy = wcsdup( data->buff );
+ wchar_t *begin, *end;
+
+ if( a )
+ *a=0;
+ if( b )
+ *b = 0;
+
+ if( !data )
+ return;
+
+ while( 1 )
+ {
+ int bc, ec;
+
+ if( expand_locate_subshell( buffcpy,
+ &begin,
+ &end,
+ 1 ) <= 0)
+ {
+ begin=buffcpy;
+ end = buffcpy + wcslen(data->buff);
+ break;
+ }
+ bc = begin-buffcpy;
+ ec = end-buffcpy;
+ if(( bc < data->buff_pos ) && (ec >= data->buff_pos) )
+ {
+ begin++;
+
+ //fwprintf( stderr, L"Subshell!\n" );
+ break;
+ }
+ *begin=0;
+ }
+ if( a )
+ *a = data->buff + (begin-buffcpy);
+ if( b )
+ *b = data->buff + (end-buffcpy);
+ free( buffcpy );
+}
+
+static void reader_current_job_or_process_extent( wchar_t **a,
+ wchar_t **b,
+ int process )
+{
+ wchar_t *begin, *end;
+ int pos;
+ wchar_t *buffcpy;
+ int finished=0;
+
+ tokenizer tok;
+
+ if( a )
+ *a=0;
+ if( b )
+ *b = 0;
+
+ reader_current_subshell_extent( &begin, &end );
+ if( !end || !begin )
+ return;
+
+ pos = data->buff_pos - (begin - data->buff);
+// fwprintf( stderr, L"Subshell extent: %d %d %d\n", begin-data->buff, end-data->buff, pos );
+
+ if( a )
+ {
+ *a = begin;
+ }
+
+ if( b )
+ {
+ *b = end;
+ }
+
+ buffcpy = wcsndup( begin, end-begin );
+
+ if( !buffcpy )
+ {
+ die_mem();
+ }
+// fwprintf( stderr, L"Strlen: %d\n", wcslen(buffcpy ) );
+
+ for( tok_init( &tok, buffcpy, TOK_ACCEPT_UNFINISHED );
+ tok_has_next( &tok ) && !finished;
+ tok_next( &tok ) )
+ {
+ int tok_begin = tok_get_pos( &tok );
+// fwprintf( stderr, L".");
+
+ switch( tok_last_type( &tok ) )
+ {
+ case TOK_PIPE:
+ if( !process )
+ break;
+
+ case TOK_END:
+ case TOK_BACKGROUND:
+ {
+
+// fwprintf( stderr, L"New cmd at %d\n", tok_begin );
+
+ if( tok_begin >= pos )
+ {
+ finished=1;
+ if( b )
+ *b = data->buff + tok_begin;
+ }
+ else
+ {
+ if( a )
+ *a = data->buff + tok_begin+1;
+ }
+ break;
+
+ }
+ }
+ }
+
+// fwprintf( stderr, L"Res: %d %d\n", *a-data->buff, *b-data->buff );
+ free( buffcpy);
+
+ tok_destroy( &tok );
+
+}
+
+void reader_current_process_extent( wchar_t **a, wchar_t **b )
+{
+ reader_current_job_or_process_extent( a, b, 1 );
+}
+
+void reader_current_job_extent( wchar_t **a, wchar_t **b )
+{
+ reader_current_job_or_process_extent( a, b, 0 );
+}
+
+
+void reader_current_token_extent( wchar_t **tok_begin,
+ wchar_t **tok_end,
+ wchar_t **prev_begin,
+ wchar_t **prev_end )
+{
+ wchar_t *begin, *end;
+ int pos;
+ wchar_t *buffcpy;
+
+ tokenizer tok;
+
+ wchar_t *a, *b, *pa, *pb;
+
+
+ a = b = pa = pb = 0;
+
+ reader_current_subshell_extent( &begin, &end );
+
+// fwprintf( stderr, L"Lalala: %d %d %d\n", begin-data->buff, end-data->buff, pos );
+
+ if( !end || !begin )
+ return;
+
+ pos = data->buff_pos - (begin - data->buff);
+
+ a = data->buff + pos;
+ b = a;
+ pa = data->buff + pos;
+ pb = pa;
+
+ buffcpy = wcsndup( begin, end-begin );
+
+ if( !buffcpy )
+ {
+ die_mem();
+ }
+
+ for( tok_init( &tok, buffcpy, TOK_ACCEPT_UNFINISHED );
+ tok_has_next( &tok );
+ tok_next( &tok ) )
+ {
+ int tok_begin = tok_get_pos( &tok );
+ int tok_end=tok_begin;
+
+ if( tok_last_type( &tok ) == TOK_STRING )
+ tok_end +=wcslen(tok_last(&tok));
+
+ if( tok_begin > pos )
+ {
+ a = b = data->buff + pos;
+ break;
+ }
+
+ if( tok_end >= pos )
+ {
+ a = begin + tok_get_pos( &tok );
+ b = a + wcslen(tok_last(&tok));
+
+// fwprintf( stderr, L"Whee %ls\n", *a );
+
+ break;
+ }
+ pa = begin + tok_get_pos( &tok );
+ pb = pa + wcslen(tok_last(&tok));
+ }
+
+// fwprintf( stderr, L"Res: %d %d\n", *a-data->buff, *b-data->buff );
+ free( buffcpy);
+
+ tok_destroy( &tok );
+
+ if( tok_begin )
+ *tok_begin = a;
+ if( tok_end )
+ *tok_end = b;
+ if( prev_begin )
+ *prev_begin = pa;
+ if( prev_end )
+ *prev_end = pb;
+
+// fwprintf( stderr, L"w00t\n" );
+
+}
+
+
+void reader_replace_current_token( wchar_t *new_token )
+{
+
+ wchar_t *begin, *end;
+ string_buffer_t sb;
+ int new_pos;
+
+ /*
+ Find current token
+ */
+ reader_current_token_extent( &begin, &end, 0, 0 );
+
+ if( !begin || !end )
+ return;
+
+// fwprintf( stderr, L"%d %d, %d\n", begin-data->buff, end-data->buff, data->buff_len );
+
+ /*
+ Make new string
+ */
+ sb_init( &sb );
+ sb_append_substring( &sb, data->buff, begin-data->buff );
+ sb_append( &sb, new_token );
+ sb_append( &sb, end );
+
+ new_pos = (begin-data->buff) + wcslen(new_token);
+
+ reader_set_buffer( (wchar_t *)sb.buff, new_pos );
+ sb_destroy( &sb );
+
+}
+
+
+/**
+ Set the specified string from the history as the current buffer. Do
+ not modify prefix_width.
+*/
+static void handle_history( const wchar_t *new_str )
+{
+ data->buff_len = wcslen( new_str );
+ check_size();
+ wcscpy( data->buff, new_str );
+ data->buff_pos=wcslen(data->buff);
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+
+ repaint();
+}
+
+/**
+ Check if the specified string is contained in the list, using
+ wcscmp as a comparison function
+*/
+static int contains( const wchar_t *needle,
+ array_list_t *haystack )
+{
+ int i;
+ for( i=0; i<al_get_count( haystack ); i++ )
+ {
+ if( !wcscmp( needle, al_get( haystack, i ) ) )
+ return 1;
+ }
+ return 0;
+
+}
+
+/**
+ Handles a token search command.
+
+ \param forward if the search should be forward or reverse
+ \param reset whether the current token should be made the new search token
+*/
+static void handle_token_history( int forward, int reset )
+{
+ wchar_t *str=0;
+ int current_pos;
+ tokenizer tok;
+
+ if(reset )
+ {
+ /*
+ Start a new token search using the current token
+ */
+
+ wchar_t *begin, *end;
+
+ reader_current_token_extent( &begin, &end, 0, 0 );
+ if( begin )
+ {
+ wcslcpy(data->search_buff, begin, end-begin+1);
+ }
+ else
+ data->search_buff[0]=0;
+
+ data->token_history_pos = -1;
+ data->search_pos=0;
+ al_foreach( &data->search_prev, (void (*)(const void *))&free );
+ al_truncate( &data->search_prev, 0 );
+ al_push( &data->search_prev, wcsdup( data->search_buff ) );
+ }
+
+ current_pos = data->token_history_pos;
+
+ if( forward || data->search_pos < (al_get_count( &data->search_prev )-1) )
+ {
+ if( forward )
+ {
+ if( data->search_pos > 0 )
+ {
+ data->search_pos--;
+ }
+ str = (wchar_t *)al_get( &data->search_prev, data->search_pos );
+ }
+ else
+ {
+ data->search_pos++;
+ str = (wchar_t *)al_get( &data->search_prev, data->search_pos );
+ }
+
+ reader_replace_current_token( str );
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+ repaint();
+ }
+ else
+ {
+ if( current_pos == -1 )
+ {
+ /*
+ Move to previous line
+ */
+ free( (void *)data->token_history_buff );
+ data->token_history_buff = wcsdup( history_prev_match(L"") );
+ current_pos = wcslen(data->token_history_buff);
+ }
+
+ if( ! wcslen( data->token_history_buff ) )
+ {
+ /*
+ We have reached the end of the history - check if the
+ history already contains the search string itself, if so
+ return, otherwise add it.
+ */
+ const wchar_t *last = al_get( &data->search_prev, al_get_count( &data->search_prev ) -1 );
+ if( wcscmp( last, data->search_buff ) )
+ {
+ str = wcsdup(data->search_buff);
+ }
+ else
+ {
+ return;
+ }
+ }
+ else
+ {
+ for( tok_init( &tok, data->token_history_buff, TOK_ACCEPT_UNFINISHED );
+ tok_has_next( &tok);
+ tok_next( &tok ))
+ {
+ switch( tok_last_type( &tok ) )
+ {
+ case TOK_STRING:
+ {
+ if( wcsstr( tok_last( &tok ), data->search_buff ) )
+ {
+// fwprintf( stderr, L"Found token at pos %d\n", tok_get_pos( &tok ) );
+ if( tok_get_pos( &tok ) >= current_pos )
+ {
+ break;
+ }
+
+ if( !contains( tok_last( &tok ), &data->search_prev ) )
+ {
+ free(str);
+ data->token_history_pos = tok_get_pos( &tok );
+ str = wcsdup(tok_last( &tok ));
+ }
+
+ }
+ }
+ }
+ }
+
+ tok_destroy( &tok );
+ }
+
+ if( str )
+ {
+ reader_replace_current_token( str );
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+ repaint();
+ al_push( &data->search_prev, str );
+ data->search_pos = al_get_count( &data->search_prev )-1;
+ }
+ else
+ {
+ data->token_history_pos=-1;
+ handle_token_history( 0, 0 );
+ }
+ }
+}
+
+
+/**
+ Move buffer position one word or erase one word. This function
+ updates both the internal buffer and the screen. It is used by
+ M-left, M-right and ^W to do block movement or block erase.
+
+ \param dir Direction to move/erase. 0 means move left, 1 means move right.
+ \param erase Whether to erase the characters along the way or only move past them.
+
+*/
+static void move_word( int dir, int erase )
+{
+ int end_buff_pos=data->buff_pos;
+ int mode=0;
+ int step = dir?1:-1;
+
+ while( mode < 2 )
+ {
+ if( !dir )
+ {
+ if( end_buff_pos == 0 )
+ break;
+ }
+ else
+ {
+ if( end_buff_pos == data->buff_len )
+ break;
+ }
+ end_buff_pos+=step;
+
+ if( end_buff_pos < data->buff_len )
+ {
+ switch( mode )
+ {
+ case 0:
+ if( iswalnum(data->buff[end_buff_pos] ) )
+ mode++;
+ break;
+
+ case 1:
+ if( !iswalnum(data->buff[end_buff_pos] ) )
+ {
+ if( !dir )
+ end_buff_pos -= step;
+ mode++;
+ }
+ break;
+/*
+ case 2:
+ if( !iswspace(data->buff[end_buff_pos] ) )
+ {
+ mode++;
+ if( !dir )
+ end_buff_pos-=step;
+ }
+ break;
+*/
+ }
+ }
+
+ if( mode==2)
+ break;
+
+ }
+
+ if( erase )
+ {
+ int remove_count = abs(data->buff_pos - end_buff_pos);
+ int first_char = mini( data->buff_pos, end_buff_pos );
+ wchar_t *woot = wcsndup( data->buff + first_char, remove_count);
+// fwprintf( stderr, L"Remove from %d to %d\n", first_char, first_char+remove_count );
+
+ kill_add( woot );
+ free( woot );
+ memmove( data->buff + first_char, data->buff + first_char+remove_count, sizeof(wchar_t)*(data->buff_len-first_char-remove_count) );
+ data->buff_len -= remove_count;
+ data->buff_pos = first_char;
+ data->buff[data->buff_len]=0;
+
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+
+ repaint();
+ }
+ else
+ {
+/* move_cursor(end_buff_pos-data->buff_pos);
+ data->buff_pos = end_buff_pos;
+*/
+ if( end_buff_pos < data->buff_pos )
+ {
+ while( data->buff_pos != end_buff_pos )
+ {
+ data->buff_pos--;
+ move_cursor( -wcwidth(data->buff[data->buff_pos]));
+ }
+ }
+ else
+ {
+ while( data->buff_pos != end_buff_pos )
+ {
+ move_cursor( wcwidth(data->buff[data->buff_pos]));
+ data->buff_pos++;
+ check_colors();
+ }
+ }
+
+ repaint();
+// check_colors();
+ }
+}
+
+
+wchar_t *reader_get_buffer()
+{
+ return data?data->buff:0;
+}
+
+void reader_set_buffer( wchar_t *b, int p )
+{
+ int l = wcslen( b );
+
+ if( !data )
+ return;
+
+ data->buff_len = l;
+ check_size();
+ wcscpy( data->buff, b );
+
+ if( p>=0 )
+ {
+ data->buff_pos=p;
+ }
+ else
+ {
+ data->buff_pos=l;
+// fwprintf( stderr, L"Pos %d\n", l );
+ }
+
+ reader_super_highlight_me_plenty( data->buff,
+ data->color,
+ data->buff_pos,
+ 0 );
+}
+
+
+int reader_get_cursor_pos()
+{
+ if( !data )
+ return -1;
+
+ return data->buff_pos;
+}
+
+
+void reader_run_command( wchar_t *cmd )
+{
+
+ wchar_t *ft;
+
+ ft= tok_first( cmd );
+
+ if( ft != 0 )
+ env_set( L"_", ft, ENV_GLOBAL );
+ free(ft);
+
+ reader_write_title();
+
+ term_donate();
+
+ if( eval( cmd, 0, TOP ) == 0 )
+ {
+ job_do_notification();
+ }
+
+ term_steal();
+
+ env_set( L"_", L"fish", ENV_GLOBAL );
+
+#ifdef HAVE__PROC_SELF_STAT
+ proc_update_jiffies();
+#endif
+
+
+}
+
+
+/**
+ Test if the given shell command contains errors. Uses parser_test
+ for testing.
+*/
+
+static int shell_test( wchar_t *b )
+{
+ return !wcslen(b);
+}
+
+/**
+ Test if the given string contains error. Since this is the error
+ detection for general purpose, there are no invalid strings, so
+ this function always returns false.
+*/
+static int default_test( wchar_t *b )
+{
+ return 0;
+}
+
+void reader_push( wchar_t *name )
+{
+ reader_data_t *n = calloc( 1, sizeof( reader_data_t ) );
+ n->name = wcsdup( name );
+ n->next = data;
+ data=n;
+ check_size();
+ data->buff[0]=data->search_buff[0]=0;
+ data->exec_prompt=1;
+
+ if( data->next == 0 )
+ {
+ reader_interactive_init();
+ }
+ reader_set_highlight_function( &highlight_universal );
+ reader_set_test_function( &default_test );
+ reader_set_prompt( L"" );
+ history_set_mode( name );
+
+ al_init( &data->search_prev );
+ data->token_history_buff=0;
+
+}
+
+void reader_pop()
+{
+ reader_data_t *n = data;
+
+ if( data == 0 )
+ {
+ debug( 0, L"Pop null reader block" );
+ sanity_lose();
+ return;
+ }
+
+ data=data->next;
+
+ free(n->name );
+ free( n->prompt );
+ free( n->buff );
+ free( n->color );
+ free( n->new_color );
+ free( n->search_buff );
+ free( n->output );
+ free( n->output_color );
+
+ /*
+ Clean up after history search
+ */
+ al_foreach( &n->search_prev, (void (*)(const void *))&free );
+ al_destroy( &n->search_prev );
+ free( (void *)n->token_history_buff);
+
+ free(n);
+
+ if( data == 0 )
+ {
+ reader_interactive_destroy();
+ }
+ else
+ {
+ history_set_mode( data->name );
+ data->exec_prompt=1;
+ }
+}
+
+void reader_set_prompt( wchar_t *new_prompt )
+{
+ free( data->prompt );
+ data->prompt=wcsdup(new_prompt);
+}
+
+void reader_set_complete_function( void (*f)( const wchar_t *,
+ array_list_t * ) )
+{
+ data->complete_func = f;
+}
+
+void reader_set_highlight_function( void (*f)( wchar_t *,
+ int *,
+ int,
+ array_list_t * ) )
+{
+ data->highlight_func = f;
+}
+
+void reader_set_test_function( int (*f)( wchar_t * ) )
+{
+ data->test_func = f;
+}
+
+/**
+ Call specified external highlighting function and then do search
+ highlighting.
+*/
+static void reader_super_highlight_me_plenty( wchar_t * buff, int *color, int pos, array_list_t *error )
+{
+ data->highlight_func( buff, color, pos, error );
+ if( wcslen(data->search_buff) )
+ {
+ wchar_t * match = wcsstr( buff, data->search_buff );
+ if( match )
+ {
+ int start = match-buff;
+ int count = wcslen(data->search_buff );
+ int i;
+// fwprintf( stderr, L"WEE color from %d to %d\n", start, start+count );
+
+ for( i=0; i<count; i++ )
+ {
+ /*
+ Do not overwrite previous highlighting color
+ */
+ if( color[start+i]>>8 == 0 )
+ {
+ color[start+i] |= HIGHLIGHT_SEARCH_MATCH<<8;
+ }
+ }
+ }
+ }
+}
+
+
+int exit_status()
+{
+ if( is_interactive )
+ return first_job == 0 && data->end_loop;
+ else
+ return end_loop;
+}
+
+/**
+ Read interactively. Read input from stdin while providing editing
+ facilities.
+*/
+static int read_i()
+{
+ int prev_end_loop=0;
+
+ reader_push(L"fish");
+ reader_set_complete_function( &complete );
+ reader_set_highlight_function( &highlight_shell );
+ reader_set_test_function( &shell_test );
+
+ data->prompt_width=60;
+
+ while( (!data->end_loop) && (!sanity_check()) )
+ {
+ wchar_t *tmp;
+
+ if( function_exists( L"fish_prompt" ) )
+ reader_set_prompt( L"fish_prompt" );
+ else
+ reader_set_prompt( DEFAULT_PROMPT );
+
+ /*
+ Put buff in temporary string and clear buff, so
+ that we can handle a call to reader_set_buffer
+ during evaluation.
+ */
+
+ tmp = wcsdup( reader_readline() );
+
+ data->buff_pos=data->buff_len=0;
+ data->buff[data->buff_len]=L'\0';
+ if( function_exists(L"fish_on_exec"))
+ {
+ eval( L"fish_on_exec", 0, TOP );
+ }
+ reader_run_command( tmp );
+ free( tmp );
+ if( function_exists(L"fish_on_return"))
+ {
+ eval( L"fish_on_return", 0, TOP );
+ }
+
+ if( data->end_loop)
+ {
+ if( !prev_end_loop && first_job != 0 )
+ {
+ writestr(L"There are stopped jobs\n");
+ write_prompt();
+ data->end_loop = 0;
+ prev_end_loop=1;
+ }
+ }
+ else
+ {
+ prev_end_loop=0;
+ }
+
+ error_reset();
+ }
+ reader_pop();
+ return 0;
+}
+
+
+
+
+wchar_t *reader_readline()
+{
+
+ wchar_t c;
+ int i;
+ int last_char=0, yank=0;
+ wchar_t *yank_str;
+ array_list_t comp;
+ int comp_empty=1;
+ int finished=0;
+ struct termios old_modes;
+
+ check_size();
+ data->search_buff[0]=data->buff[data->buff_len]='\0';
+
+
+ al_init( &comp );
+
+ data->exec_prompt=1;
+
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+ repaint();
+
+ tcgetattr(0,&old_modes); /* get the current terminal modes */
+ if( tcsetattr(0,TCSANOW,&shell_modes)) /* set the new modes */
+ {
+ wperror(L"tcsetattr");
+ exit(1);
+ }
+
+ while( !finished && !data->end_loop)
+ {
+
+ /*
+ Save the terminal status so we know if we have to redraw
+ */
+
+ reader_save_status();
+
+ /*
+ Sometimes strange input sequences seem to generate a zero
+ byte. I believe these simply mean a character was pressed
+ but it should be ignored. (Example: Trying to add a tilde
+ (~) to digit)
+ */
+ check_winch();
+ while( (c=input_readch()) == 0 )
+ ;
+
+ check_winch();
+ reader_check_status();
+
+ if( (last_char == R_COMPLETE) && (c != R_COMPLETE) && (!comp_empty) )
+ {
+ al_foreach( &comp, (void (*)(const void *))&free );
+ al_truncate( &comp, 0 );
+ comp_empty = 1;
+ }
+
+ if( last_char != R_YANK && last_char != R_YANK_POP )
+ yank=0;
+ switch (c)
+ {
+
+ /* go to beginning of line*/
+ case R_BEGINNING_OF_LINE:
+ {
+ data->buff_pos = 0;
+
+ repaint();
+ break;
+ }
+
+ /* go to EOL*/
+ case R_END_OF_LINE:
+ {
+ data->buff_pos = data->buff_len;
+
+ repaint();
+ break;
+ }
+
+ case R_NULL:
+ {
+ data->exec_prompt=1;
+ repaint();
+ break;
+ }
+
+ /* complete */
+ case R_COMPLETE:
+ {
+
+// fwprintf( stderr, L"aaa\n" );
+ if( !data->complete_func )
+ break;
+
+ if( !comp_empty && last_char == R_COMPLETE )
+ break;
+
+ if( comp_empty )
+ {
+ wchar_t *begin, *end;
+ wchar_t *buffcpy;
+
+ reader_current_subshell_extent( &begin, &end );
+
+ int len = data->buff_pos - (data->buff - begin);
+ buffcpy = wcsndup( begin, len );
+
+ //fwprintf( stderr, L"String is %ls\n", buffcpy );
+
+ reader_save_status();
+ data->complete_func( buffcpy, &comp );
+ reader_check_status();
+
+ sort_list( &comp );
+ remove_duplicates( &comp );
+
+ free( buffcpy );
+ }
+ if( (comp_empty =
+ handle_completions( &comp ) ) )
+ {
+ al_foreach( &comp, (void (*)(const void *))&free );
+ al_truncate( &comp, 0 );
+ }
+
+ break;
+ }
+
+ /* kill*/
+ case R_KILL_LINE:
+ {
+ kill_add( &data->buff[data->buff_pos] );
+ data->buff_len = data->buff_pos;
+ data->buff[data->buff_len]=L'\0';
+
+
+ repaint();
+// wcscpy(data->search_buff,data->buff);
+ break;
+ }
+
+ case R_BACKWARD_KILL_LINE:
+ {
+ wchar_t prev = data->buff[data->buff_pos];
+ data->buff[data->buff_pos]=0;
+ kill_add( data->buff );
+ data->buff[data->buff_pos]=prev;
+ data->buff_len = wcslen(data->buff +data->buff_pos);
+ memmove( data->buff, data->buff +data->buff_pos, sizeof(wchar_t)*data->buff_len );
+ data->buff[data->buff_len]=L'\0';
+ data->buff_pos=0;
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+
+ repaint();
+// wcscpy(data->search_buff,data->buff);
+ break;
+ }
+
+ case R_KILL_WHOLE_LINE:
+ {
+ kill_add( data->buff );
+ data->buff_len = data->buff_pos = 0;
+ data->buff[data->buff_len]=L'\0';
+ reader_super_highlight_me_plenty( data->buff, data->color, data->buff_pos, 0 );
+
+ repaint();
+// wcscpy(data->search_buff,data->buff);
+ break;
+ }
+
+ /* yank*/
+ case R_YANK:
+ yank_str = kill_yank();
+ insert_str( yank_str );
+ yank = wcslen( yank_str );
+// wcscpy(data->search_buff,data->buff);
+ break;
+
+ /* rotate killring*/
+ case R_YANK_POP:
+ if( yank )
+ {
+ for( i=0; i<yank; i++ )
+ remove_backward();
+
+ yank_str = kill_yank_rotate();
+ insert_str(yank_str);
+ yank = wcslen(yank_str);
+ }
+ break;
+
+ /* Escape was pressed */
+ case L'\e':
+ if( *data->search_buff )
+ {
+ history_reset();
+ wcscpy( data->buff, data->search_buff );
+ data->buff_pos = data->buff_len = wcslen(data->buff);
+ *data->search_buff=0;
+ check_colors();
+
+ }
+
+ break;
+
+ /* delete backward*/
+ case R_BACKWARD_DELETE_CHAR:
+ remove_backward();
+ break;
+
+ /* delete forward*/
+ case R_DELETE_CHAR:
+ remove_forward();
+ break;
+
+ /* exit, but only if line is empty */
+ case R_EXIT:
+
+ if( data->buff_len == 0 )
+ {
+ writestr( L"\n" );
+ data->end_loop=1;
+ }
+ break;
+
+ /* Newline, evaluate*/
+ case L'\n':
+ {
+ data->buff[data->buff_len]=L'\0';
+
+ if( !data->test_func( data->buff ) )
+ {
+
+ if( wcslen( data->buff ) )
+ {
+// wcscpy(data->search_buff,L"");
+ history_add( data->buff );
+ }
+ finished=1;
+ data->buff_pos=data->buff_len;
+ check_colors();
+ writestr( L"\n" );
+ }
+ else
+ repaint();
+
+ break;
+ }
+
+ /* History up*/
+ case R_HISTORY_SEARCH_BACKWARD:
+// fwprintf( stderr, L"Search history for \'%ls\' %d long\n", data->search_buff, wcslen(data->search_buff) );
+ if( (last_char != R_HISTORY_SEARCH_BACKWARD) &&
+ (last_char != R_HISTORY_SEARCH_FORWARD) )
+ {
+ wcscpy(data->search_buff, data->buff );
+ data->search_buff[data->buff_pos]=0;
+ }
+
+ handle_history(history_prev_match(data->search_buff));
+ break;
+
+ /* History down*/
+ case R_HISTORY_SEARCH_FORWARD:
+ if( (last_char != R_HISTORY_SEARCH_BACKWARD) &&
+ (last_char != R_HISTORY_SEARCH_FORWARD) )
+ {
+ wcscpy(data->search_buff, data->buff );
+ data->search_buff[data->buff_pos]=0;
+ }
+
+ handle_history(history_next_match(data->search_buff));
+ break;
+
+ case R_HISTORY_TOKEN_SEARCH_BACKWARD:
+ {
+ int reset=0;
+ if( (last_char != R_HISTORY_TOKEN_SEARCH_BACKWARD) &&
+ (last_char != R_HISTORY_TOKEN_SEARCH_FORWARD) )
+ {
+ reset=1;
+
+ }
+
+ handle_token_history( 0, reset );
+
+ break;
+ }
+
+ case R_HISTORY_TOKEN_SEARCH_FORWARD:
+ {
+ int reset=0;
+
+ if( (last_char != R_HISTORY_TOKEN_SEARCH_BACKWARD) &&
+ (last_char != R_HISTORY_TOKEN_SEARCH_FORWARD) )
+ {
+ reset=1;
+ }
+
+ handle_token_history( 1, reset );
+
+ break;
+ }
+
+
+
+ /* Move left*/
+ case R_BACKWARD_CHAR:
+ if( data->buff_pos > 0 )
+ {
+ data->buff_pos--;
+ if( !force_repaint() )
+ {
+ move_cursor( -wcwidth(data->buff[data->buff_pos]));
+ check_colors();
+ }
+ else
+ {
+ repaint();
+ }
+ }
+ break;
+
+ /* Move right*/
+ case R_FORWARD_CHAR:
+ if( data->buff_pos < data->buff_len )
+ {
+ if( !force_repaint() )
+ {
+ move_cursor( wcwidth(data->buff[data->buff_pos]));
+ data->buff_pos++;
+ check_colors();
+ }
+ else
+ {
+ data->buff_pos++;
+
+ repaint();
+ }
+ }
+ break;
+
+ case R_DELETE_LINE:
+ data->buff[0]=0;
+ data->buff_len=0;
+ data->buff_pos=0;
+ repaint();
+
+ /* kill one word left */
+ case R_BACKWARD_KILL_WORD:
+ move_word(0,1);
+ break;
+
+ /* kill one word right */
+ case R_KILL_WORD:
+ move_word(1,1);
+ break;
+
+ /* move one word left*/
+ case R_BACKWARD_WORD:
+ move_word(0,0);
+ break;
+
+ /* move one word right*/
+ case R_FORWARD_WORD:
+ move_word( 1,0);
+ break;
+
+ case R_CLEAR_SCREEN:
+ {
+ writembs( clear_screen );
+
+ repaint();
+ break;
+ }
+
+ case R_BEGINNING_OF_HISTORY:
+ {
+ history_first();
+ break;
+ }
+
+ case R_END_OF_HISTORY:
+ {
+ history_reset();
+
+ break;
+ }
+
+ /* Other, if a normal character, we add it to the command */
+ default:
+ {
+ if( (c< WCHAR_END) && (c>31) && (c != 127) )
+ {
+ insert_char( c );
+ }
+ break;
+ }
+
+ }
+
+ if( (c != R_HISTORY_SEARCH_BACKWARD) &&
+ (c != R_HISTORY_SEARCH_FORWARD) &&
+ (c != R_HISTORY_TOKEN_SEARCH_BACKWARD) &&
+ (c != R_HISTORY_TOKEN_SEARCH_FORWARD) )
+ {
+ data->search_buff[0]=0;
+ history_reset();
+ }
+
+
+ last_char = c;
+ }
+
+ al_destroy( &comp );
+ if( tcsetattr(0,TCSANOW,&old_modes)) /* return to previous mode */
+ {
+ wperror(L"tcsetattr");
+ exit(1);
+ }
+
+ set_color( FISH_COLOR_RESET, FISH_COLOR_RESET );
+
+ return data->buff;
+}
+
+/**
+ Read non-interactively. Read input from stdin without displaying
+ the prompt, using syntax highlighting. This is used for reading
+ scripts and init files.
+*/
+static int read_ni()
+{
+ FILE *in_stream;
+ wchar_t *buff=0;
+ buffer_t acc;
+
+ int des = dup( 0 );
+ int res=0;
+
+ if (des == -1)
+ {
+ wperror( L"dup" );
+ return 1;
+ }
+
+ b_init( &acc );
+
+ in_stream = fdopen( des, "r" );
+ if( in_stream != 0 )
+ {
+ wchar_t *str;
+
+ while(!feof( in_stream ))
+ {
+ char buff[4096];
+ int c = fread(buff, 1, 4096, in_stream);
+ b_append( &acc, buff, c );
+ }
+ b_append( &acc, "\0", 1 );
+ str = str2wcs( acc.buff );
+ b_destroy( &acc );
+
+// fwprintf( stderr, L"Woot is %d chars\n", wcslen( acc.buff ) );
+
+ if( str )
+ {
+ if( !parser_test( str, 1 ) )
+ {
+ //fwprintf( stderr, L"We parse it\n" );
+ eval( str, 0, TOP );
+ }
+ else
+ {
+ /*
+ No error reporting - parser_test did that for us
+ */
+ res = 1;
+ }
+ free( str );
+ }
+ else
+ {
+ if( acc.used > 1 )
+ {
+ debug( 1,
+ L"Could not convert input. Read %d bytes.",
+ acc.used-1 );
+ }
+ else
+ {
+ debug( 1,
+ L"Could not read input stream" );
+ }
+ res=1;
+ }
+
+ if( fclose( in_stream ))
+ {
+ debug( 1,
+ L"Error while closing input" );
+ wperror( L"fclose" );
+ res = 1;
+ }
+
+ }
+ else
+ {
+ debug( 1,
+ L"Error while opening input" );
+ wperror( L"fdopen" );
+ free( buff );
+ res=1;
+ }
+ error_reset();
+ return res;
+}
+
+int reader_read()
+{
+ int res;
+ /*
+ If reader_read is called recursively through the '.' builtin,
+ we need to preserve is_interactive, so we save the
+ original state. We also update the signal handlers.
+ */
+ int shell_was_interactive = is_interactive;
+ is_interactive = isatty(STDIN_FILENO);
+ set_signal_handlers();
+
+ res= is_interactive?read_i():read_ni();
+
+ /*
+ If the exit command was called in a script, only exit the
+ script, not the program
+ */
+ end_loop = 0;
+
+ is_interactive = shell_was_interactive;
+ set_signal_handlers();
+ return res;
+}