/* replace a range of lines with the joined text of those lines */ bool join_lines( const int from, const int to, const bool isglobal ) { static char * buf = 0; static int bufsz = 0; int size = 0; line_t * const ep = search_line_node( inc_addr( to ) ); line_t * bp = search_line_node( from ); while( bp != ep ) { const char * const s = get_sbuf_line( bp ); if( !s || !resize_buffer( &buf, &bufsz, size + bp->len ) ) return false; memcpy( buf + size, s, bp->len ); size += bp->len; bp = bp->q_forw; } if( !resize_buffer( &buf, &bufsz, size + 2 ) ) return false; memcpy( buf + size, "\n", 2 ); size += 2; if( !delete_lines( from, to, isglobal ) ) return false; current_addr_ = from - 1; disable_interrupts(); if( !put_sbuf_line( buf, size, current_addr_ ) || !push_undo_atom( UADD, current_addr_, current_addr_ ) ) { enable_interrupts(); return false; } modified_ = true; enable_interrupts(); return true; }
/* copy a range of lines; return false if error */ bool copy_lines( const int first_addr, const int second_addr, const int addr ) { line_t *lp, *np = search_line_node( first_addr ); undo_t * up = 0; int n = second_addr - first_addr + 1; int m = 0; current_addr_ = addr; if( addr >= first_addr && addr < second_addr ) { n = addr - first_addr + 1; m = second_addr - addr; } for( ; n > 0; n = m, m = 0, np = search_line_node( current_addr_ + 1 ) ) for( ; n-- > 0; np = np->q_forw ) { disable_interrupts(); lp = dup_line_node( np ); if( !lp ) { enable_interrupts(); return false; } add_line_node( lp, current_addr_++ ); if( up ) up->tail = lp; else { up = push_undo_atom( UADD, current_addr_, current_addr_ ); if( !up ) { enable_interrupts(); return false; } } modified_ = true; enable_interrupts(); } return true; }
/* for each line in a range, change text matching a pattern according to a substitution template; return false if error */ char search_and_replace( const int first_addr, const int second_addr, const int gflags, const int snum, const char isglobal ) { int lc; char match_found = 0; set_current_addr( first_addr - 1 ); for( lc = 0; lc <= second_addr - first_addr; ++lc ) { line_t *lp = search_line_node( inc_current_addr() ); int len = replace_matching_text( lp, gflags, snum ); if( len < 0 ) return 0; if( len ) { const char *txt = rbuf; const char *eot = rbuf + len; undo_t *up = 0; disable_interrupts(); if( !delete_lines( current_addr(), current_addr(), isglobal ) ) return 0; do { txt = put_sbuf_line( txt, current_addr() ); if( !txt ) { enable_interrupts(); return 0; } if( up ) up->tail = search_line_node( current_addr() ); else if( !( up = push_undo_atom( UADD, -1, -1 ) ) ) { enable_interrupts(); return 0; } } while( txt != eot ); enable_interrupts(); match_found = 1; } } if( !match_found && !( gflags & GLB ) ) { set_error_msg( "No match" ); return 0; } return 1; }
/* delete a range of lines */ bool delete_lines( const int from, const int to, const bool isglobal ) { line_t *n, *p; if( !yank_lines( from, to ) ) return false; disable_interrupts(); if( !push_undo_atom( UDEL, from, to ) ) { enable_interrupts(); return false; } n = search_line_node( inc_addr( to ) ); p = search_line_node( from - 1 ); /* this search_line_node last! */ if( isglobal ) unset_active_nodes( p->q_forw, n ); link_nodes( p, n ); last_addr_ -= to - from + 1; current_addr_ = from - 1; modified_ = true; enable_interrupts(); return true; }
/* copy a range of lines to the cut buffer */ bool yank_lines( const int from, const int to ) { line_t * const ep = search_line_node( inc_addr( to ) ); line_t * bp = search_line_node( from ); line_t * lp = &yank_buffer_head; line_t * p; clear_yank_buffer(); while( bp != ep ) { disable_interrupts(); p = dup_line_node( bp ); if( !p ) { enable_interrupts(); return false; } insert_node( p, lp ); bp = bp->q_forw; lp = p; enable_interrupts(); } return true; }
/* return pointer to intialized undo node */ undo_t * push_undo_atom( const int type, const int from, const int to ) { disable_interrupts(); if( !resize_undo_buffer( &ustack, &usize, ( u_ptr + 1 ) * sizeof (undo_t) ) ) { show_strerror( 0, errno ); set_error_msg( "Memory exhausted" ); if( ustack ) { clear_undo_stack(); free( ustack ); ustack = 0; usize = u_ptr = 0; u_current_addr = u_last_addr = -1; } enable_interrupts(); return 0; } enable_interrupts(); ustack[u_ptr].type = type; ustack[u_ptr].tail = search_line_node( to ); ustack[u_ptr].head = search_line_node( from ); return ustack + u_ptr++; }
/* undo last change to the editor buffer */ bool undo( const bool isglobal ) { int n; const int o_current_addr = current_addr_; const int o_last_addr = last_addr_; const bool o_modified = modified_; if( u_ptr <= 0 || u_current_addr < 0 || u_last_addr < 0 ) { set_error_msg( "Nothing to undo" ); return false; } search_line_node( 0 ); /* reset cached value */ disable_interrupts(); for( n = u_ptr - 1; n >= 0; --n ) { switch( ustack[n].type ) { case UADD: link_nodes( ustack[n].head->q_back, ustack[n].tail->q_forw ); break; case UDEL: link_nodes( ustack[n].head->q_back, ustack[n].head ); link_nodes( ustack[n].tail, ustack[n].tail->q_forw ); break; case UMOV: case VMOV: link_nodes( ustack[n-1].head, ustack[n].head->q_forw ); link_nodes( ustack[n].tail->q_back, ustack[n-1].tail ); link_nodes( ustack[n].head, ustack[n].tail ); --n; break; } ustack[n].type ^= 1; } /* reverse undo stack order */ for( n = 0; 2 * n < u_ptr - 1; ++n ) { undo_t tmp = ustack[n]; ustack[n] = ustack[u_ptr-1-n]; ustack[u_ptr-1-n] = tmp; } if( isglobal ) clear_active_list(); current_addr_ = u_current_addr; u_current_addr = o_current_addr; last_addr_ = u_last_addr; u_last_addr = o_last_addr; modified_ = u_modified; u_modified = o_modified; enable_interrupts(); return true; }
/* return the address of the next line matching a pattern in a given direction. wrap around begin/end of editor buffer if necessary */ int get_matching_node_addr( const char **ibufpp, const char forward ) { regex_t *pat = get_compiled_pattern( ibufpp ); int addr = current_addr(); if( !pat ) return -1; do { addr = ( forward ? inc_addr( addr ) : dec_addr( addr ) ); if( addr ) { line_t *lp = search_line_node( addr ); char *s = get_sbuf_line( lp ); if( !s ) return -1; if( isbinary() ) nul_to_newline( s, lp->len ); if( !regexec( pat, s, 0, 0, 0 ) ) return addr; } } while( addr != current_addr() ); set_error_msg( "No match" ); return -1; }
/* move a range of lines */ bool move_lines( const int first_addr, const int second_addr, const int addr, const bool isglobal ) { line_t *b1, *a1, *b2, *a2; int n = inc_addr( second_addr ); int p = first_addr - 1; disable_interrupts(); if( addr == first_addr - 1 || addr == second_addr ) { a2 = search_line_node( n ); b2 = search_line_node( p ); current_addr_ = second_addr; } else if( !push_undo_atom( UMOV, p, n ) || !push_undo_atom( UMOV, addr, inc_addr( addr ) ) ) { enable_interrupts(); return false; } else { a1 = search_line_node( n ); if( addr < first_addr ) { b1 = search_line_node( p ); b2 = search_line_node( addr ); /* this search_line_node last! */ } else { b2 = search_line_node( addr ); b1 = search_line_node( p ); /* this search_line_node last! */ } a2 = b2->q_forw; link_nodes( b2, b1->q_forw ); link_nodes( a1->q_back, a2 ); link_nodes( b1, a1 ); current_addr_ = addr + ( ( addr < first_addr ) ? second_addr - first_addr + 1 : 0 ); } if( isglobal ) unset_active_nodes( b2->q_forw, a2 ); modified_ = true; enable_interrupts(); return true; }
/* Insert text from stdin (or from command buffer if global) to after line n; stop when either a single period is read or EOF. Return false if insertion fails. */ bool append_lines( const char ** const ibufpp, const int addr, const bool isglobal ) { int size = 0; undo_t * up = 0; current_addr_ = addr; while( true ) { if( !isglobal ) { *ibufpp = get_tty_line( &size ); if( !*ibufpp ) return false; if( size == 0 || (*ibufpp)[size-1] != '\n' ) { clearerr( stdin ); return ( size == 0 ); } } else { if( !**ibufpp ) return true; for( size = 0; (*ibufpp)[size++] != '\n'; ) ; } if( size == 2 && **ibufpp == '.' ) { *ibufpp += size; return true; } disable_interrupts(); if( !put_sbuf_line( *ibufpp, size, current_addr_ ) ) { enable_interrupts(); return false; } if( up ) up->tail = search_line_node( current_addr_ ); else { up = push_undo_atom( UADD, current_addr_, current_addr_ ); if( !up ) { enable_interrupts(); return false; } } *ibufpp += size; modified_ = true; enable_interrupts(); } }
/* add line matching a pattern to the global-active list */ char build_active_list( const char **ibufpp, const int first_addr, const int second_addr, const char match ) { regex_t *pat; line_t *lp; int addr; const char delimiter = **ibufpp; if( delimiter == ' ' || delimiter == '\n' ) { set_error_msg( "Invalid pattern delimiter" ); return 0; } if( !( pat = get_compiled_pattern( ibufpp ) ) ) return 0; if( **ibufpp == delimiter ) ++(*ibufpp); clear_active_list(); lp = search_line_node( first_addr ); for( addr = first_addr; addr <= second_addr; ++addr, lp = lp->q_forw ) { char *s = get_sbuf_line( lp ); if( !s ) return 0; if( isbinary() ) nul_to_newline( s, lp->len ); if( !regexec( pat, s, 0, 0, 0 ) == match && !set_active_node( lp ) ) return 0; } return 1; }
/* add a line node in the editor buffer after the given line */ static void add_line_node( line_t * const lp, const int addr ) { line_t * const prev = search_line_node( addr ); insert_node( lp, prev ); ++last_addr_; }
/* execute the next command in command buffer; return error status */ static int exec_command( const char ** const ibufpp, const int prev_status, const bool isglobal ) { const char * fnp; int gflags = 0; int addr, c, n; const int addr_cnt = extract_addr_range( ibufpp ); if( addr_cnt < 0 ) return ERR; *ibufpp = skip_blanks( *ibufpp ); c = *(*ibufpp)++; switch( c ) { case 'a': if( !get_command_suffix( ibufpp, &gflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !append_lines( ibufpp, second_addr, isglobal ) ) return ERR; break; case 'c': if( first_addr == 0 ) first_addr = 1; if( second_addr == 0 ) second_addr = 1; if( !check_current_addr( addr_cnt ) || !get_command_suffix( ibufpp, &gflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !delete_lines( first_addr, second_addr, isglobal ) || !append_lines( ibufpp, current_addr(), isglobal ) ) return ERR; break; case 'd': if( !check_current_addr( addr_cnt ) || !get_command_suffix( ibufpp, &gflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !delete_lines( first_addr, second_addr, isglobal ) ) return ERR; inc_current_addr(); break; case 'e': if( modified() && !scripted() && prev_status != EMOD ) return EMOD; /* fall through */ case 'E': if( unexpected_address( addr_cnt ) || unexpected_command_suffix( **ibufpp ) ) return ERR; fnp = get_filename( ibufpp ); if( !fnp || !delete_lines( 1, last_addr(), isglobal ) || !close_sbuf() ) return ERR; if( !open_sbuf() ) return FATAL; if( fnp[0] && fnp[0] != '!' ) set_def_filename( fnp ); if( traditional() && !fnp[0] && !def_filename[0] ) { set_error_msg( "No current filename" ); return ERR; } if( read_file( fnp[0] ? fnp : def_filename, 0 ) < 0 ) return ERR; reset_undo_state(); set_modified( false ); break; case 'f': if( unexpected_address( addr_cnt ) || unexpected_command_suffix( **ibufpp ) ) return ERR; fnp = get_filename( ibufpp ); if( !fnp ) return ERR; if( fnp[0] == '!' ) { set_error_msg( "Invalid redirection" ); return ERR; } if( fnp[0] ) set_def_filename( fnp ); printf( "%s\n", strip_escapes( def_filename ) ); break; case 'g': case 'v': case 'G': case 'V': if( isglobal ) { set_error_msg( "Cannot nest global commands" ); return ERR; } n = ( c == 'g' || c == 'G' ); /* mark matching lines */ if( !check_addr_range( 1, last_addr(), addr_cnt ) || !build_active_list( ibufpp, first_addr, second_addr, n ) ) return ERR; n = ( c == 'G' || c == 'V' ); /* interactive */ if( ( n && !get_command_suffix( ibufpp, &gflags ) ) || !exec_global( ibufpp, gflags, n ) ) return ERR; break; case 'h': case 'H': if( unexpected_address( addr_cnt ) || !get_command_suffix( ibufpp, &gflags ) ) return ERR; if( c == 'H' ) verbose = !verbose; if( ( c == 'h' || verbose ) && errmsg[0] ) fprintf( stderr, "%s\n", errmsg ); break; case 'i': if( second_addr == 0 ) second_addr = 1; if( !get_command_suffix( ibufpp, &gflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !append_lines( ibufpp, second_addr - 1, isglobal ) ) return ERR; break; case 'j': if( !check_addr_range( current_addr(), current_addr() + 1, addr_cnt ) || !get_command_suffix( ibufpp, &gflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( first_addr != second_addr && !join_lines( first_addr, second_addr, isglobal ) ) return ERR; break; case 'k': n = *(*ibufpp)++; if( second_addr == 0 ) { invalid_address(); return ERR; } if( !get_command_suffix( ibufpp, &gflags ) || !mark_line_node( search_line_node( second_addr ), n ) ) return ERR; break; case 'l': case 'n': case 'p': if( c == 'l' ) n = GLS; else if( c == 'n' ) n = GNP; else n = GPR; if( !check_current_addr( addr_cnt ) || !get_command_suffix( ibufpp, &gflags ) || !display_lines( first_addr, second_addr, gflags | n ) ) return ERR; gflags = 0; break; case 'm': if( !check_current_addr( addr_cnt ) || !get_third_addr( ibufpp, &addr ) ) return ERR; if( addr >= first_addr && addr < second_addr ) { set_error_msg( "Invalid destination" ); return ERR; } if( !get_command_suffix( ibufpp, &gflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !move_lines( first_addr, second_addr, addr, isglobal ) ) return ERR; break; case 'P': case 'q': case 'Q': if( unexpected_address( addr_cnt ) || !get_command_suffix( ibufpp, &gflags ) ) return ERR; if( c == 'P' ) prompt_on = !prompt_on; else if( modified() && !scripted() && c == 'q' && prev_status != EMOD ) return EMOD; else return QUIT; break; case 'r': if( unexpected_command_suffix( **ibufpp ) ) return ERR; if( addr_cnt == 0 ) second_addr = last_addr(); fnp = get_filename( ibufpp ); if( !fnp ) return ERR; if( !isglobal ) clear_undo_stack(); if( !def_filename[0] && fnp[0] != '!' ) set_def_filename( fnp ); if( traditional() && !fnp[0] && !def_filename[0] ) { set_error_msg( "No current filename" ); return ERR; } addr = read_file( fnp[0] ? fnp : def_filename, second_addr ); if( addr < 0 ) return ERR; if( addr ) set_modified( true ); break; case 's': if( !command_s( ibufpp, &gflags, addr_cnt, isglobal ) ) return ERR; break; case 't': if( !check_current_addr( addr_cnt ) || !get_third_addr( ibufpp, &addr ) || !get_command_suffix( ibufpp, &gflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !copy_lines( first_addr, second_addr, addr ) ) return ERR; break; case 'u': if( unexpected_address( addr_cnt ) || !get_command_suffix( ibufpp, &gflags ) || !undo( isglobal ) ) return ERR; break; case 'w': case 'W': n = **ibufpp; if( n == 'q' || n == 'Q' ) ++*ibufpp; if( unexpected_command_suffix( **ibufpp ) ) return ERR; fnp = get_filename( ibufpp ); if( !fnp ) return ERR; if( addr_cnt == 0 && last_addr() == 0 ) first_addr = second_addr = 0; else if( !check_addr_range( 1, last_addr(), addr_cnt ) ) return ERR; if( !def_filename[0] && fnp[0] != '!' ) set_def_filename( fnp ); if( traditional() && !fnp[0] && !def_filename[0] ) { set_error_msg( "No current filename" ); return ERR; } addr = write_file( fnp[0] ? fnp : def_filename, ( c == 'W' ) ? "a" : "w", first_addr, second_addr ); if( addr < 0 ) return ERR; if( addr == last_addr() ) set_modified( false ); else if( modified() && !scripted() && n == 'q' && prev_status != EMOD ) return EMOD; if( n == 'q' || n == 'Q' ) return QUIT; break; case 'x': if( second_addr < 0 || last_addr() < second_addr ) { invalid_address(); return ERR; } if( !get_command_suffix( ibufpp, &gflags ) ) return ERR; if( !isglobal ) clear_undo_stack(); if( !put_lines( second_addr ) ) return ERR; break; case 'y': if( !check_current_addr( addr_cnt ) || !get_command_suffix( ibufpp, &gflags ) || !yank_lines( first_addr, second_addr ) ) return ERR; break; case 'z': first_addr = 1; if( !check_addr_range( first_addr, current_addr() + ( traditional() || !isglobal ), addr_cnt ) ) return ERR; if( **ibufpp > '0' && **ibufpp <= '9' ) { if( parse_int( &n, *ibufpp, ibufpp ) ) set_window_lines( n ); else return ERR; } if( !get_command_suffix( ibufpp, &gflags ) || !display_lines( second_addr, min( last_addr(), second_addr + window_lines() ), gflags ) ) return ERR; gflags = 0; break; case '=': if( !get_command_suffix( ibufpp, &gflags ) ) return ERR; printf( "%d\n", addr_cnt ? second_addr : last_addr() ); break; case '!': if( unexpected_address( addr_cnt ) ) return ERR; fnp = get_shell_command( ibufpp ); if( !fnp ) return ERR; if( system( fnp + 1 ) < 0 ) { set_error_msg( "Can't create shell process" ); return ERR; } if( !scripted() ) printf( "!\n" ); break; case '\n': first_addr = 1; if( !check_addr_range( first_addr, current_addr() + ( traditional() || !isglobal ), addr_cnt ) || !display_lines( second_addr, second_addr, 0 ) ) return ERR; break; case '#': while( *(*ibufpp)++ != '\n' ) ; break; default : set_error_msg( "Unknown command" ); return ERR; } if( gflags && !display_lines( current_addr(), current_addr(), gflags ) ) return ERR; return 0; }