Imported Upstream version 1.12
[platform/upstream/ed.git] / main_loop.c
1 /*  GNU ed - The GNU line editor.
2     Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
3     Copyright (C) 2006-2015 Antonio Diaz Diaz.
4
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <ctype.h>
20 #include <setjmp.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include "ed.h"
26
27
28 enum Status { QUIT = -1, ERR = -2, EMOD = -3, FATAL = -4 };
29
30 static char def_filename[1024] = "";    /* default filename */
31 static char errmsg[80] = "";            /* error message buffer */
32 static char prompt_str[80] = "*";       /* command prompt */
33 static int first_addr = 0, second_addr = 0;
34 static bool prompt_on = false;          /* if set, show command prompt */
35 static bool verbose = false;            /* if set, print all error messages */
36
37
38 void set_def_filename( const char * const s )
39   {
40   strncpy( def_filename, s, sizeof def_filename );
41   def_filename[sizeof(def_filename)-1] = 0;
42   }
43
44 void set_error_msg( const char * msg )
45   {
46   if( !msg ) msg = "";
47   strncpy( errmsg, msg, sizeof errmsg );
48   errmsg[sizeof(errmsg)-1] = 0;
49   }
50
51 void set_prompt( const char * const s )
52   {
53   prompt_on = true;
54   strncpy( prompt_str, s, sizeof prompt_str );
55   prompt_str[sizeof(prompt_str)-1] = 0;
56   }
57
58 void set_verbose( void ) { verbose = true; }
59
60
61 static const line_t * mark[26];                 /* line markers */
62 static int markno;                              /* line marker count */
63
64 static bool mark_line_node( const line_t * const lp, int c )
65   {
66   c -= 'a';
67   if( c < 0 || c >= 26 )
68     { set_error_msg( "Invalid mark character" ); return false; }
69   if( !mark[c] ) ++markno;
70   mark[c] = lp;
71   return true;
72   }
73
74
75 void unmark_line_node( const line_t * const lp )
76   {
77   int i;
78   for( i = 0; markno && i < 26; ++i )
79     if( mark[i] == lp )
80       { mark[i] = 0; --markno; }
81   }
82
83
84 /* return address of a marked line */
85 static int get_marked_node_addr( int c )
86   {
87   c -= 'a';
88   if( c < 0 || c >= 26 )
89     { set_error_msg( "Invalid mark character" ); return -1; }
90   return get_line_node_addr( mark[c]);
91   }
92
93
94 /* Returns pointer to copy of shell command in the command buffer */
95 static const char * get_shell_command( const char ** const ibufpp )
96   {
97   static char * buf = 0;
98   static int bufsz = 0;
99   static char * shcmd = 0;              /* shell command buffer */
100   static int shcmdsz = 0;               /* shell command buffer size */
101   static int shcmdlen = 0;              /* shell command length */
102   const char * p;                       /* substitution char pointer */
103   int i = 0, len;
104
105   if( restricted() ) { set_error_msg( "Shell access restricted" ); return 0; }
106   if( !get_extended_line( ibufpp, &len, true ) ) return 0;
107   p = *ibufpp;
108   if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return 0;
109   buf[i++] = '!';                       /* prefix command w/ bang */
110   while( **ibufpp != '\n' )
111     {
112     if( **ibufpp == '!' )
113       {
114       if( p != *ibufpp )
115         {
116         if( !resize_buffer( &buf, &bufsz, i + 1 ) ) return 0;
117         buf[i++] = *(*ibufpp)++;
118         }
119       else if( !shcmd || ( traditional() && !*( shcmd + 1 ) ) )
120         { set_error_msg( "No previous command" ); return 0; }
121       else
122         {
123         if( !resize_buffer( &buf, &bufsz, i + shcmdlen ) ) return 0;
124         for( p = shcmd + 1; p < shcmd + shcmdlen; ) buf[i++] = *p++;
125         p = (*ibufpp)++;
126         }
127       }
128     else if( **ibufpp == '%' )
129       {
130       if( !def_filename[0] )
131         { set_error_msg( "No current filename" ); return 0; }
132       p = strip_escapes( def_filename );
133       len = strlen( p );
134       if( !resize_buffer( &buf, &bufsz, i + len ) ) return 0;
135       while( len-- ) buf[i++] = *p++;
136       p = (*ibufpp)++;
137       }
138     else
139       {
140       if( !resize_buffer( &buf, &bufsz, i + 2 ) ) return 0;
141       buf[i++] = **ibufpp;
142       if( *(*ibufpp)++ == '\\' ) buf[i++] = *(*ibufpp)++;
143       }
144     }
145   while( **ibufpp == '\n' ) ++*ibufpp;                  /* skip newline */
146   if( !resize_buffer( &shcmd, &shcmdsz, i + 1 ) ) return 0;
147   memcpy( shcmd, buf, i );
148   shcmdlen = i; shcmd[i] = 0;
149   if( *p == '!' || *p == '%' ) printf( "%s\n", shcmd + 1 );
150   return shcmd;
151   }
152
153
154 static const char * skip_blanks( const char * p )
155   {
156   while( isspace( (unsigned char)*p ) && *p != '\n' ) ++p;
157   return p;
158   }
159
160
161 /* Returns pointer to copy of filename in the command buffer */
162 static const char * get_filename( const char ** const ibufpp )
163   {
164   static char * buf = 0;
165   static int bufsz = 0;
166   const int pmax = path_max( 0 );
167   int n;
168
169   *ibufpp = skip_blanks( *ibufpp );
170   if( **ibufpp != '\n' )
171     {
172     int size = 0;
173     if( !get_extended_line( ibufpp, &size, true ) ) return 0;
174     if( **ibufpp == '!' )
175       {
176       ++*ibufpp;
177       return get_shell_command( ibufpp );
178       }
179     else if( size > pmax )
180       { set_error_msg( "Filename too long" ); return 0; }
181     }
182   else if( !traditional() && !def_filename[0] )
183     { set_error_msg( "No current filename" ); return 0; }
184   if( !resize_buffer( &buf, &bufsz, pmax + 1 ) ) return 0;
185   for( n = 0; **ibufpp != '\n'; ++n, ++*ibufpp ) buf[n] = **ibufpp;
186   buf[n] = 0;
187   while( **ibufpp == '\n' ) ++*ibufpp;                  /* skip newline */
188   return ( may_access_filename( buf ) ? buf : 0 );
189   }
190
191
192 static void invalid_address( void ) { set_error_msg( "Invalid address" ); }
193
194
195 /* return the next line address in the command buffer */
196 static int next_addr( const char ** const ibufpp, int * const addr_cnt )
197   {
198   const char * const s = *ibufpp = skip_blanks( *ibufpp );
199   int addr = current_addr();
200   bool first = true;                    /* true == addr, false == offset */
201
202   while( true )
203     {
204     int n;
205     const unsigned char ch = **ibufpp;
206     if( isdigit( ch ) )
207       {
208       if( !first ) { invalid_address(); return -2; };
209       if( !parse_int( &addr, *ibufpp, ibufpp ) ) return -2;
210       }
211     else switch( ch )
212       {
213       case '+':
214       case '\t':
215       case ' ':
216       case '-': *ibufpp = skip_blanks( ++*ibufpp );
217                 if( isdigit( (unsigned char)**ibufpp ) )
218                   {
219                   if( !parse_int( &n, *ibufpp, ibufpp ) ) return -2;
220                   addr += ( ( ch == '-' ) ? -n : n );
221                   }
222                 else if( ch == '+' ) ++addr;
223                 else if( ch == '-' ) --addr;
224                 break;
225       case '.':
226       case '$': if( !first ) { invalid_address(); return -2; };
227                 ++*ibufpp;
228                 addr = ( ( ch == '.' ) ? current_addr() : last_addr() );
229                 break;
230       case '/':
231       case '?': if( !first ) { invalid_address(); return -2; };
232                 addr = next_matching_node_addr( ibufpp, ch == '/' );
233                 if( addr < 0 ) return -2;
234                 if( ch == **ibufpp ) ++*ibufpp;
235                 break;
236       case '\'':if( !first ) { invalid_address(); return -2; };
237                 ++*ibufpp;
238                 addr = get_marked_node_addr( *(*ibufpp)++ );
239                 if( addr < 0 ) return -2;
240                 break;
241       case '%':
242       case ',':
243       case ';': if( first )
244                   {
245                   ++*ibufpp; ++*addr_cnt;
246                   second_addr = ( ( ch == ';' ) ? current_addr() : 1 );
247                   addr = last_addr();
248                   break;
249                   }                             /* FALL THROUGH */
250       default : if( *ibufpp == s ) return -1;   /* EOF */
251                 if( addr < 0 || addr > last_addr() )
252                   { invalid_address(); return -2; }
253                 ++*addr_cnt; return addr;
254       }
255     first = false;
256     }
257   }
258
259
260 /* get line addresses from the command buffer until an invalid address
261    is seen. Returns the number of addresses read */
262 static int extract_addr_range( const char ** const ibufpp )
263   {
264   int addr;
265   int addr_cnt = 0;
266
267   first_addr = second_addr = current_addr();
268   while( true )
269     {
270     addr = next_addr( ibufpp, &addr_cnt );
271     if( addr < 0 ) break;
272     first_addr = second_addr; second_addr = addr;
273     if( **ibufpp != ',' && **ibufpp != ';' ) break;
274     if( **ibufpp == ';' ) set_current_addr( addr );
275     ++*ibufpp;
276     }
277   if( addr_cnt == 1 || second_addr != addr ) first_addr = second_addr;
278   return ( ( addr != -2 ) ? addr_cnt : -1 );
279   }
280
281
282 /* get a valid address from the command buffer */
283 static bool get_third_addr( const char ** const ibufpp, int * const addr )
284   {
285   const int old1 = first_addr;
286   const int old2 = second_addr;
287   int addr_cnt = extract_addr_range( ibufpp );
288
289   if( addr_cnt < 0 ) return false;
290   if( traditional() && addr_cnt == 0 )
291     { set_error_msg( "Destination expected" ); return false; }
292   if( second_addr < 0 || second_addr > last_addr() )
293     { invalid_address(); return false; }
294   *addr = second_addr;
295   first_addr = old1; second_addr = old2;
296   return true;
297   }
298
299
300 /* return true if address range is valid */
301 static bool check_addr_range( const int n, const int m, const int addr_cnt )
302   {
303   if( addr_cnt == 0 )
304     {
305     first_addr = n;
306     second_addr = m;
307     }
308   if( first_addr < 1 || first_addr > second_addr || second_addr > last_addr() )
309     { invalid_address(); return false; }
310   return true;
311   }
312
313
314 /* return true if current address is valid */
315 static bool check_current_addr( const int addr_cnt )
316   {
317   return check_addr_range( current_addr(), current_addr(), addr_cnt );
318   }
319
320
321 /* verify the command suffix in the command buffer */
322 static bool get_command_suffix( const char ** const ibufpp,
323                                 int * const gflagsp )
324   {
325   while( true )
326     {
327     const char ch = **ibufpp;
328     if( ch == 'l' ) *gflagsp |= GLS;
329     else if( ch == 'n' ) *gflagsp |= GNP;
330     else if( ch == 'p' ) *gflagsp |= GPR;
331     else break;
332     ++*ibufpp;
333     }
334   if( *(*ibufpp)++ != '\n' )
335     { set_error_msg( "Invalid command suffix" ); return false; }
336   return true;
337   }
338
339
340 static bool unexpected_address( const int addr_cnt )
341   {
342   if( addr_cnt > 0 ) { set_error_msg( "Unexpected address" ); return true; }
343   return false;
344   }
345
346 static bool unexpected_command_suffix( const unsigned char ch )
347   {
348   if( !isspace( ch ) )
349     { set_error_msg( "Unexpected command suffix" ); return true; }
350   return false;
351   }
352
353
354 static bool command_s( const char ** const ibufpp, int * const gflagsp,
355                        const int addr_cnt, const bool isglobal )
356   {
357   static int gflags = 0;
358   static int snum = 0;
359   enum Sflags {
360     SGG = 0x01,         /* complement previous global substitute suffix */
361     SGP = 0x02,         /* complement previous print suffix */
362     SGR = 0x04,         /* use last regex instead of last pat */
363     SGF = 0x08          /* repeat last substitution */
364     } sflags = 0;
365
366   do {
367     if( isdigit( (unsigned char)**ibufpp ) )
368       {
369       if( !parse_int( &snum, *ibufpp, ibufpp ) ) return false;
370       sflags |= SGF; gflags &= ~GSG;            /* override GSG */
371       }
372     else switch( **ibufpp )
373       {
374       case '\n':sflags |= SGF; break;
375       case 'g': sflags |= SGG; ++*ibufpp; break;
376       case 'p': sflags |= SGP; ++*ibufpp; break;
377       case 'r': sflags |= SGR; ++*ibufpp; break;
378       default : if( sflags )
379                   { set_error_msg( "Invalid command suffix" ); return false; }
380       }
381     }
382   while( sflags && **ibufpp != '\n' );
383   if( sflags && !prev_pattern() )
384     { set_error_msg( "No previous substitution" ); return false; }
385   if( sflags & SGG ) snum = 0;                  /* override numeric arg */
386   if( **ibufpp != '\n' && (*ibufpp)[1] == '\n' )
387     { set_error_msg( "Invalid pattern delimiter" ); return false; }
388   if( ( !sflags || ( sflags & SGR ) ) && !new_compiled_pattern( ibufpp ) )
389     return false;
390   if( !sflags && !extract_subst_tail( ibufpp, &gflags, &snum, isglobal ) )
391     return false;
392   if( isglobal ) gflags |= GLB;
393   else gflags &= ~GLB;
394   if( sflags & SGG ) gflags ^= GSG;
395   if( sflags & SGP ) { gflags ^= GPR; gflags &= ~( GLS | GNP ); }
396   switch( **ibufpp )
397     {
398     case 'l': gflags |= GLS; ++*ibufpp; break;
399     case 'n': gflags |= GNP; ++*ibufpp; break;
400     case 'p': gflags |= GPR; ++*ibufpp; break;
401     }
402   if( !check_current_addr( addr_cnt ) ||
403       !get_command_suffix( ibufpp, gflagsp ) ) return false;
404   if( !isglobal ) clear_undo_stack();
405   if( !search_and_replace( first_addr, second_addr, gflags, snum, isglobal ) )
406     return false;
407   if( ( gflags & ( GPR | GLS | GNP ) ) &&
408       !display_lines( current_addr(), current_addr(), gflags ) )
409     return false;
410   return true;
411   }
412
413
414 static bool exec_global( const char ** const ibufpp, const int gflags,
415                          const bool interactive );
416
417 /* execute the next command in command buffer; return error status */
418 static int exec_command( const char ** const ibufpp, const int prev_status,
419                          const bool isglobal )
420   {
421   const char * fnp;
422   int gflags = 0;
423   int addr, c, n;
424   const int addr_cnt = extract_addr_range( ibufpp );
425
426   if( addr_cnt < 0 ) return ERR;
427   *ibufpp = skip_blanks( *ibufpp );
428   c = *(*ibufpp)++;
429   switch( c )
430     {
431     case 'a': if( !get_command_suffix( ibufpp, &gflags ) ) return ERR;
432               if( !isglobal ) clear_undo_stack();
433               if( !append_lines( ibufpp, second_addr, isglobal ) ) return ERR;
434               break;
435     case 'c': if( first_addr == 0 ) first_addr = 1;
436               if( second_addr == 0 ) second_addr = 1;
437               if( !check_current_addr( addr_cnt ) ||
438                   !get_command_suffix( ibufpp, &gflags ) ) return ERR;
439               if( !isglobal ) clear_undo_stack();
440               if( !delete_lines( first_addr, second_addr, isglobal ) ||
441                   !append_lines( ibufpp, current_addr(), isglobal ) ) return ERR;
442               break;
443     case 'd': if( !check_current_addr( addr_cnt ) ||
444                   !get_command_suffix( ibufpp, &gflags ) ) return ERR;
445               if( !isglobal ) clear_undo_stack();
446               if( !delete_lines( first_addr, second_addr, isglobal ) ) return ERR;
447               inc_current_addr();
448               break;
449     case 'e': if( modified() && !scripted() && prev_status != EMOD )
450                 return EMOD;                            /* fall through */
451     case 'E': if( unexpected_address( addr_cnt ) ||
452                   unexpected_command_suffix( **ibufpp ) ) return ERR;
453               fnp = get_filename( ibufpp );
454               if( !fnp || !delete_lines( 1, last_addr(), isglobal ) ||
455                   !close_sbuf() ) return ERR;
456               if( !open_sbuf() ) return FATAL;
457               if( fnp[0] && fnp[0] != '!' ) set_def_filename( fnp );
458               if( traditional() && !fnp[0] && !def_filename[0] )
459                 { set_error_msg( "No current filename" ); return ERR; }
460               if( read_file( fnp[0] ? fnp : def_filename, 0 ) < 0 )
461                 return ERR;
462               reset_undo_state(); set_modified( false );
463               break;
464     case 'f': if( unexpected_address( addr_cnt ) ||
465                   unexpected_command_suffix( **ibufpp ) ) return ERR;
466               fnp = get_filename( ibufpp );
467               if( !fnp ) return ERR;
468               if( fnp[0] == '!' )
469                 { set_error_msg( "Invalid redirection" ); return ERR; }
470               if( fnp[0] ) set_def_filename( fnp );
471               printf( "%s\n", strip_escapes( def_filename ) );
472               break;
473     case 'g':
474     case 'v':
475     case 'G':
476     case 'V': if( isglobal )
477                 { set_error_msg( "Cannot nest global commands" ); return ERR; }
478               n = ( c == 'g' || c == 'G' );     /* mark matching lines */
479               if( !check_addr_range( 1, last_addr(), addr_cnt ) ||
480                   !build_active_list( ibufpp, first_addr, second_addr, n ) )
481                 return ERR;
482               n = ( c == 'G' || c == 'V' );             /* interactive */
483               if( ( n && !get_command_suffix( ibufpp, &gflags ) ) ||
484                   !exec_global( ibufpp, gflags, n ) )
485                 return ERR;
486               break;
487     case 'h':
488     case 'H': if( unexpected_address( addr_cnt ) ||
489                   !get_command_suffix( ibufpp, &gflags ) ) return ERR;
490               if( c == 'H' ) verbose = !verbose;
491               if( ( c == 'h' || verbose ) && errmsg[0] )
492                 fprintf( stderr, "%s\n", errmsg );
493               break;
494     case 'i': if( second_addr == 0 ) second_addr = 1;
495               if( !get_command_suffix( ibufpp, &gflags ) ) return ERR;
496               if( !isglobal ) clear_undo_stack();
497               if( !append_lines( ibufpp, second_addr - 1, isglobal ) )
498                 return ERR;
499               break;
500     case 'j': if( !check_addr_range( current_addr(), current_addr() + 1, addr_cnt ) ||
501                   !get_command_suffix( ibufpp, &gflags ) ) return ERR;
502               if( !isglobal ) clear_undo_stack();
503               if( first_addr != second_addr &&
504                   !join_lines( first_addr, second_addr, isglobal ) ) return ERR;
505               break;
506     case 'k': n = *(*ibufpp)++;
507               if( second_addr == 0 ) { invalid_address(); return ERR; }
508               if( !get_command_suffix( ibufpp, &gflags ) ||
509                   !mark_line_node( search_line_node( second_addr ), n ) )
510                 return ERR;
511               break;
512     case 'l':
513     case 'n':
514     case 'p': if( c == 'l' ) n = GLS; else if( c == 'n' ) n = GNP; else n = GPR;
515               if( !check_current_addr( addr_cnt ) ||
516                   !get_command_suffix( ibufpp, &gflags ) ||
517                   !display_lines( first_addr, second_addr, gflags | n ) )
518                 return ERR;
519               gflags = 0;
520               break;
521     case 'm': if( !check_current_addr( addr_cnt ) ||
522                   !get_third_addr( ibufpp, &addr ) ) return ERR;
523               if( addr >= first_addr && addr < second_addr )
524                 { set_error_msg( "Invalid destination" ); return ERR; }
525               if( !get_command_suffix( ibufpp, &gflags ) ) return ERR;
526               if( !isglobal ) clear_undo_stack();
527               if( !move_lines( first_addr, second_addr, addr, isglobal ) )
528                 return ERR;
529               break;
530     case 'P':
531     case 'q':
532     case 'Q': if( unexpected_address( addr_cnt ) ||
533                   !get_command_suffix( ibufpp, &gflags ) ) return ERR;
534               if( c == 'P' ) prompt_on = !prompt_on;
535               else if( modified() && !scripted() && c == 'q' &&
536                        prev_status != EMOD ) return EMOD;
537               else return QUIT;
538               break;
539     case 'r': if( unexpected_command_suffix( **ibufpp ) ) return ERR;
540               if( addr_cnt == 0 ) second_addr = last_addr();
541               fnp = get_filename( ibufpp );
542               if( !fnp ) return ERR;
543               if( !isglobal ) clear_undo_stack();
544               if( !def_filename[0] && fnp[0] != '!' ) set_def_filename( fnp );
545               if( traditional() && !fnp[0] && !def_filename[0] )
546                 { set_error_msg( "No current filename" ); return ERR; }
547               addr = read_file( fnp[0] ? fnp : def_filename, second_addr );
548               if( addr < 0 ) return ERR;
549               if( addr ) set_modified( true );
550               break;
551     case 's': if( !command_s( ibufpp, &gflags, addr_cnt, isglobal ) )
552                 return ERR;
553               break;
554     case 't': if( !check_current_addr( addr_cnt ) ||
555                   !get_third_addr( ibufpp, &addr ) ||
556                   !get_command_suffix( ibufpp, &gflags ) ) return ERR;
557               if( !isglobal ) clear_undo_stack();
558               if( !copy_lines( first_addr, second_addr, addr ) ) return ERR;
559               break;
560     case 'u': if( unexpected_address( addr_cnt ) ||
561                   !get_command_suffix( ibufpp, &gflags ) ||
562                   !undo( isglobal ) ) return ERR;
563               break;
564     case 'w':
565     case 'W': n = **ibufpp;
566               if( n == 'q' || n == 'Q' ) ++*ibufpp;
567               if( unexpected_command_suffix( **ibufpp ) ) return ERR;
568               fnp = get_filename( ibufpp );
569               if( !fnp ) return ERR;
570               if( addr_cnt == 0 && last_addr() == 0 )
571                 first_addr = second_addr = 0;
572               else if( !check_addr_range( 1, last_addr(), addr_cnt ) )
573                 return ERR;
574               if( !def_filename[0] && fnp[0] != '!' ) set_def_filename( fnp );
575               if( traditional() && !fnp[0] && !def_filename[0] )
576                 { set_error_msg( "No current filename" ); return ERR; }
577               addr = write_file( fnp[0] ? fnp : def_filename,
578                      ( c == 'W' ) ? "a" : "w", first_addr, second_addr );
579               if( addr < 0 ) return ERR;
580               if( addr == last_addr() ) set_modified( false );
581               else if( modified() && !scripted() && n == 'q' &&
582                        prev_status != EMOD ) return EMOD;
583               if( n == 'q' || n == 'Q' ) return QUIT;
584               break;
585     case 'x': if( second_addr < 0 || last_addr() < second_addr )
586                 { invalid_address(); return ERR; }
587               if( !get_command_suffix( ibufpp, &gflags ) ) return ERR;
588               if( !isglobal ) clear_undo_stack();
589               if( !put_lines( second_addr ) ) return ERR;
590               break;
591     case 'y': if( !check_current_addr( addr_cnt ) ||
592                   !get_command_suffix( ibufpp, &gflags ) ||
593                   !yank_lines( first_addr, second_addr ) ) return ERR;
594               break;
595     case 'z': first_addr = 1;
596               if( !check_addr_range( first_addr, current_addr() +
597                                      ( traditional() || !isglobal ), addr_cnt ) )
598                 return ERR;
599               if( **ibufpp > '0' && **ibufpp <= '9' )
600                 { if( parse_int( &n, *ibufpp, ibufpp ) ) set_window_lines( n );
601                   else return ERR; }
602               if( !get_command_suffix( ibufpp, &gflags ) ||
603                   !display_lines( second_addr,
604                     min( last_addr(), second_addr + window_lines() - 1 ),
605                     gflags ) )
606                 return ERR;
607               gflags = 0;
608               break;
609     case '=': if( !get_command_suffix( ibufpp, &gflags ) ) return ERR;
610               printf( "%d\n", addr_cnt ? second_addr : last_addr() );
611               break;
612     case '!': if( unexpected_address( addr_cnt ) ) return ERR;
613               fnp = get_shell_command( ibufpp );
614               if( !fnp ) return ERR;
615               if( system( fnp + 1 ) < 0 )
616                 { set_error_msg( "Can't create shell process" ); return ERR; }
617               if( !scripted() ) fputs( "!\n", stdout );
618               break;
619     case '\n': first_addr = 1;
620               if( !check_addr_range( first_addr, current_addr() +
621                                      ( traditional() || !isglobal ), addr_cnt ) ||
622                   !display_lines( second_addr, second_addr, 0 ) )
623                 return ERR;
624               break;
625     case '#': while( *(*ibufpp)++ != '\n' ) ;
626               break;
627     default : set_error_msg( "Unknown command" ); return ERR;
628     }
629   if( gflags && !display_lines( current_addr(), current_addr(), gflags ) )
630     return ERR;
631   return 0;
632   }
633
634
635 /* apply command list in the command buffer to the active lines in a
636    range; return false if error */
637 static bool exec_global( const char ** const ibufpp, const int gflags,
638                          const bool interactive )
639   {
640   static char * buf = 0;
641   static int bufsz = 0;
642   const char * cmd = 0;
643
644   if( !interactive )
645     {
646     if( traditional() && !strcmp( *ibufpp, "\n" ) )
647       cmd = "p\n";                      /* null cmd_list == 'p' */
648     else
649       {
650       if( !get_extended_line( ibufpp, 0, false ) ) return false;
651       cmd = *ibufpp;
652       }
653     }
654   clear_undo_stack();
655   while( true )
656     {
657     const line_t * const lp = next_active_node();
658     if( !lp ) break;
659     set_current_addr( get_line_node_addr( lp ) );
660     if( current_addr() < 0 ) return false;
661     if( interactive )
662       {
663       /* print current_addr; get a command in global syntax */
664       int len;
665       if( !display_lines( current_addr(), current_addr(), gflags ) )
666         return false;
667       do { *ibufpp = get_tty_line( &len ); }
668       while( *ibufpp && len > 0 && (*ibufpp)[len-1] != '\n' );
669       if( !*ibufpp ) return false;
670       if( len == 0 )
671         { set_error_msg( "Unexpected end-of-file" ); return false; }
672       if( len == 1 && !strcmp( *ibufpp, "\n" ) ) continue;
673       if( len == 2 && !strcmp( *ibufpp, "&\n" ) )
674         { if( !cmd ) { set_error_msg( "No previous command" ); return false; } }
675       else
676         {
677         if( !get_extended_line( ibufpp, &len, false ) ||
678             !resize_buffer( &buf, &bufsz, len + 1 ) ) return false;
679         memcpy( buf, *ibufpp, len + 1 );
680         cmd = buf;
681         }
682       }
683     *ibufpp = cmd;
684     while( **ibufpp ) if( exec_command( ibufpp, 0, true ) < 0 ) return false;
685     }
686   return true;
687   }
688
689
690 int main_loop( const bool loose )
691   {
692   extern jmp_buf jmp_state;
693   const char * ibufp;                   /* pointer to command buffer */
694   volatile int err_status = 0;          /* program exit status */
695   volatile int linenum = 0;             /* script line number */
696   int len, status;
697
698   disable_interrupts();
699   set_signals();
700   status = setjmp( jmp_state );
701   if( !status ) enable_interrupts();
702   else { status = -1; fputs( "\n?\n", stderr ); set_error_msg( "Interrupt" ); }
703
704   while( true )
705     {
706     fflush( stdout );
707     if( status < 0 && verbose )
708       { fprintf( stderr, "%s\n", errmsg ); fflush( stderr ); }
709     if( prompt_on ) { fputs( prompt_str, stdout ); fflush( stdout ); }
710     ibufp = get_tty_line( &len );
711     if( !ibufp ) return err_status;
712     if( !len )
713       {
714       if( !modified() || scripted() ) return err_status;
715       fputs( "?\n", stderr ); set_error_msg( "Warning: buffer modified" );
716       if( is_regular_file( 0 ) )
717         {
718         if( verbose ) fprintf( stderr, "script, line %d: %s\n", linenum, errmsg );
719         return 2;
720         }
721       set_modified( false ); status = EMOD; continue;
722       }
723     else if( ibufp[len-1] != '\n' )     /* discard line */
724       { set_error_msg( "Unexpected end-of-file" ); status = ERR; continue; }
725     else ++linenum;
726     status = exec_command( &ibufp, status, false );
727     if( status == 0 ) continue;
728     if( status == QUIT ) return err_status;
729     if( status == EMOD )
730       {
731       fputs( "?\n", stderr );                           /* give warning */
732       set_error_msg( "Warning: buffer modified" );
733       if( is_regular_file( 0 ) )
734         {
735         if( verbose )
736           fprintf( stderr, "script, line %d: %s\n", linenum, errmsg );
737         return 1;
738         }
739       }
740     else if( status == FATAL )
741       {
742       if( verbose )
743         {
744         if( is_regular_file( 0 ) )
745           fprintf( stderr, "script, line %d: %s\n", linenum, errmsg );
746         else fprintf( stderr, "%s\n", errmsg );
747         }
748       return 1;
749       }
750     else
751       {
752       fputs( "?\n", stderr );                   /* give warning */
753       if( is_regular_file( 0 ) )
754         {
755         if( verbose )
756           fprintf( stderr, "script, line %d: %s\n", linenum, errmsg );
757         return 1;
758         }
759       }
760     if( !loose ) err_status = 1;
761     }
762   }