static void do_scrolling (struct frame *frame, struct glyph_matrix *current_matrix, struct matrix_elt *matrix, int window_size, int unchanged_at_top) { struct matrix_elt *p; int i, j, k; USE_SAFE_ALLOCA; /* True if we have set a terminal window with set_terminal_window. */ bool terminal_window_p = 0; /* A queue for line insertions to be done. */ struct queue { int count, pos; }; struct queue *queue_start; SAFE_NALLOCA (queue_start, 1, current_matrix->nrows); struct queue *queue = queue_start; char *retained_p = SAFE_ALLOCA (window_size); int *copy_from; SAFE_NALLOCA (copy_from, 1, window_size); /* Zero means line is empty. */ memset (retained_p, 0, window_size * sizeof (char)); for (k = 0; k < window_size; ++k) copy_from[k] = -1; #ifdef GLYPH_DEBUG # define CHECK_BOUNDS \ do \ { \ int ck; \ for (ck = 0; ck < window_size; ++ck) \ eassert (copy_from[ck] == -1 \ || (copy_from[ck] >= 0 && copy_from[ck] < window_size)); \ } \ while (0); #endif /* When j is advanced, this corresponds to deleted lines. When i is advanced, this corresponds to inserted lines. */ i = j = window_size; while (i > 0 || j > 0) { p = matrix + i * (window_size + 1) + j; if (p->insertcost < p->writecost && p->insertcost < p->deletecost) { /* Insert should be done at vpos i-1, plus maybe some before. Queue the screen operation to be performed. */ queue->count = p->insertcount; queue->pos = i + unchanged_at_top - p->insertcount; ++queue; /* By incrementing I, we leave room in the result rows for the empty rows opened up. */ i -= p->insertcount; } else if (p->deletecost < p->writecost) { /* Old line at vpos j-1, and maybe some before it, should be deleted. By decrementing J, we skip some lines in the temp_rows which is equivalent to omitting these lines in the result rows, thus deleting them. */ j -= p->deletecount; /* Set the terminal window, if not done already. */ if (! terminal_window_p) { set_terminal_window (frame, window_size + unchanged_at_top); terminal_window_p = 1; } /* Delete lines on the terminal. */ ins_del_lines (frame, j + unchanged_at_top, - p->deletecount); } else { /* Best thing done here is no insert or delete, i.e. a write. */ --i, --j; eassert (i >= 0 && i < window_size); eassert (j >= 0 && j < window_size); copy_from[i] = j; retained_p[j] = 1; #ifdef GLYPH_DEBUG CHECK_BOUNDS; #endif } } /* Now do all insertions queued above. */ if (queue > queue_start) { int next = -1; /* Set the terminal window if not yet done. */ if (!terminal_window_p) { set_terminal_window (frame, window_size + unchanged_at_top); terminal_window_p = 1; } do { --queue; /* Do the deletion on the terminal. */ ins_del_lines (frame, queue->pos, queue->count); /* All lines in the range deleted become empty in the glyph matrix. Assign to them glyph rows that are not retained. K is the starting position of the deleted range relative to the window we are working in. */ k = queue->pos - unchanged_at_top; for (j = 0; j < queue->count; ++j) { /* Find the next row not retained. */ while (retained_p[++next]) ; /* Record that this row is to be used for the empty glyph row j. */ copy_from[k + j] = next; } } while (queue > queue_start); } for (k = 0; k < window_size; ++k) eassert (copy_from[k] >= 0 && copy_from[k] < window_size); /* Perform the row swizzling. */ mirrored_line_dance (current_matrix, unchanged_at_top, window_size, copy_from, retained_p); /* Some sanity checks if GLYPH_DEBUG is defined. */ CHECK_MATRIX (current_matrix); if (terminal_window_p) set_terminal_window (frame, 0); SAFE_FREE (); }
static void do_direct_scrolling (struct frame *frame, struct glyph_matrix *current_matrix, struct matrix_elt *cost_matrix, int window_size, int unchanged_at_top) { struct matrix_elt *p; int i, j; USE_SAFE_ALLOCA; /* A queue of deletions and insertions to be performed. */ struct alt_queue { int count, pos, window; }; struct alt_queue *queue_start; SAFE_NALLOCA (queue_start, 1, window_size); struct alt_queue *queue = queue_start; /* True if a terminal window has been set with set_terminal_window. */ bool terminal_window_p = 0; /* If true, a write has been selected, allowing either an insert or a delete to be selected next. If false, a delete cannot be selected unless j < i, and an insert cannot be selected unless i < j. This corresponds to a similar restriction (with the ordering reversed) in calculate_direct_scrolling, which is intended to ensure that lines marked as inserted will be blank. */ bool write_follows_p = 1; /* For each row in the new matrix what row of the old matrix it is. */ int *copy_from; SAFE_NALLOCA (copy_from, 1, window_size); /* Non-zero for each row in the new matrix that is retained from the old matrix. Lines not retained are empty. */ char *retained_p = SAFE_ALLOCA (window_size); memset (retained_p, 0, window_size * sizeof (char)); /* Perform some sanity checks when GLYPH_DEBUG is on. */ CHECK_MATRIX (current_matrix); /* We are working on the line range UNCHANGED_AT_TOP ... UNCHANGED_AT_TOP + WINDOW_SIZE (not including) in CURRENT_MATRIX. We step through lines in this range from the end to the start. I is an index into new lines, j an index into old lines. The cost matrix determines what to do for ranges of indices. If i is decremented without also decrementing j, this corresponds to inserting empty lines in the result. If j is decremented without also decrementing i, this corresponds to omitting these lines in the new rows, i.e. rows are deleted. */ i = j = window_size; while (i > 0 || j > 0) { p = cost_matrix + i * (window_size + 1) + j; if (p->insertcost < p->writecost && p->insertcost < p->deletecost && (write_follows_p || i < j)) { /* Insert is cheaper than deleting or writing lines. Leave a hole in the result display that will be filled with empty lines when the queue is emptied. */ queue->count = 0; queue->window = i; queue->pos = i - p->insertcount; ++queue; i -= p->insertcount; write_follows_p = 0; } else if (p->deletecost < p->writecost && (write_follows_p || i > j)) { /* Deleting lines is cheaper. By decrementing J, omit deletecount lines from the original. */ write_follows_p = 0; j -= p->deletecount; } else { /* One or more lines should be written. In the direct scrolling method we do this by scrolling the lines to the place they belong. */ int n_to_write = p->writecount; write_follows_p = 1; eassert (n_to_write > 0); if (i > j) { /* Immediately insert lines */ set_terminal_window (frame, i + unchanged_at_top); terminal_window_p = 1; ins_del_lines (frame, j - n_to_write + unchanged_at_top, i - j); } else if (i < j) { /* Queue the deletion of a group of lines */ queue->pos = i - n_to_write + unchanged_at_top; queue->window = j + unchanged_at_top; queue->count = i - j; ++queue; } while (n_to_write > 0) { --i, --j, --n_to_write; copy_from[i] = j; retained_p[j] = 1; } } } /* Do queued operations. */ if (queue > queue_start) { int next = -1; do { --queue; if (queue->count) { set_terminal_window (frame, queue->window); terminal_window_p = 1; ins_del_lines (frame, queue->pos, queue->count); } else { for (j = queue->window - 1; j >= queue->pos; --j) { while (retained_p[++next]) ; copy_from[j] = next; } } } while (queue > queue_start); } /* Now, for each row I in the range of rows we are working on, copy_from[i] gives the original line to copy to I, and retained_p[copy_from[i]] is zero if line I in the new display is empty. */ mirrored_line_dance (current_matrix, unchanged_at_top, window_size, copy_from, retained_p); if (terminal_window_p) set_terminal_window (frame, 0); SAFE_FREE (); }
int main(int argc, char **argv) { char *locale = setlocale(LC_ALL, ""); for(int i = 0; i < 256; i++) localised_up_case[i] = toupper(i); if (locale) { struct re_pattern_buffer re_pb; struct re_registers re_reg; memset(&re_pb, 0, sizeof re_pb); memset(&re_reg, 0, sizeof re_reg); re_pb.translate = localised_up_case; re_compile_pattern(LOCALE_REGEX, strlen(LOCALE_REGEX), &re_pb); if (re_search(&re_pb, locale, strlen(locale), 0, strlen(locale), &re_reg) >= 0) { if (re_reg.start[1] >= 0) io_utf8 = true; } free(re_reg.start); free(re_reg.end); } bool no_config = false; char *macro_name = NULL, *key_bindings_name = NULL, *menu_conf_name = NULL, *startup_prefs_name = DEF_PREFS_NAME; char * const skiplist = calloc(argc, 1); if (!skiplist) exit(1); /* We need this many flags. */ for(int i = 1; i < argc; i++) { if (argv[i][0] == '-' && (!strcmp(&argv[i][1], "h") || !strcmp(&argv[i][1], "-help" "\0" VERSION_STRING))) { puts(ARG_HELP); exit(0); } /* Special arguments start with two dashes. If we find one, we cancel its entry in argv[], so that it will be skipped when opening the specified files. The only exception is +N for skipping to the N-th line. */ if (argv[i][0] == '-' && argv[i][1] == '-') { if (!argv[i][2]) i++; /* You can use "--" to force the next token to be a filename */ else if (!strcmp(&argv[i][2], "noconfig") || !strcmp(&argv[i][2], "no-config")) { no_config = true; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "noansi") || !strcmp(&argv[i][2], "no-ansi")) { ansi = false; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "no-syntax")) { do_syntax = false; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "prefs")) { if (i < argc-1) { startup_prefs_name = argv[i+1]; skiplist[i] = skiplist[i+1] = 1; /* argv[i] = argv[i+1] = NULL; */ } } else if (!strcmp(&argv[i][2], "ansi")) { ansi = true; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "utf8")) { io_utf8 = true; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "no-utf8")) { io_utf8 = false; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "macro")) { if (i < argc-1) { macro_name = argv[i+1]; skiplist[i] = skiplist[i+1] = 1; /* argv[i] = argv[i+1] = NULL; */ } } else if (!strcmp(&argv[i][2], "keys")) { if (i < argc-1) { key_bindings_name = argv[i+1]; skiplist[i] = skiplist[i+1] = 1; /* argv[i] = argv[i+1] = NULL; */ } } else if (!strcmp(&argv[i][2], "menus")) { if (i < argc-1) { menu_conf_name = argv[i+1]; skiplist[i] = skiplist[i+1] = 1; /* argv[i] = argv[i+1] = NULL; */ } } } } #ifdef NE_TEST /* Dump the builtin menu and key bindings to compare to doc/default.menus and doc/default.keys. */ int dump_config(void); dump_config(); #endif /* Unless --noconfig was specified, we try to configure the menus and the keyboard. Note that these functions can exit() on error. */ if (!no_config) { get_menu_configuration(menu_conf_name); get_key_bindings(key_bindings_name); } /* If we cannot even create a buffer, better go... */ if (!new_buffer()) exit(1); /* Now that key_bindings are loaded, try to fix up the message for NOT_FOUND. */ { char *repeat_last_keystroke, *new_not_found; if ((repeat_last_keystroke = find_key_strokes(REPEATLAST_A, 1))) { if ((new_not_found = malloc(39+strlen(repeat_last_keystroke)))) { strcat(strcat(strcpy(new_not_found, "Not Found. (RepeatLast with "), repeat_last_keystroke), " to wrap.)"); error_msg[NOT_FOUND] = new_not_found; } free(repeat_last_keystroke); } } clear_buffer(cur_buffer); /* The INT_MAX clip always exists, and it is used by the Through command. */ clip_desc * const cd = alloc_clip_desc(INT_MAX, 0); if (!cd) exit(1); add_head(&clips, &cd->cd_node); /* General terminfo and cursor motion initalization. From here onwards, we cannot exit() lightly. */ term_init(); /* We will be always using the last line for the status bar. */ set_terminal_window(ne_lines-1); /* We read in all the key capabilities. */ read_key_capabilities(); /* Some initializations of other modules... */ re_set_syntax( RE_CONTEXT_INDEP_ANCHORS | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE | RE_NEWLINE_ALT | RE_NO_BK_PARENS | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES ); bool first_file = true; load_virtual_extensions(); load_auto_prefs(cur_buffer, startup_prefs_name); buffer *stdin_buffer = NULL; if (!isatty(fileno(stdin))) { first_file = false; const int error = load_fd_in_buffer(cur_buffer, fileno(stdin)); print_error(error); stdin_buffer = cur_buffer; if (!(freopen("/dev/tty", "r", stdin))) { fprintf(stderr, "Cannot reopen input tty\n"); abort(); } } /* The terminal is prepared for interactive I/O. */ set_interactive_mode(); clear_entire_screen(); /* This function sets fatal_code() as signal interrupt handler for all the dangerous signals (SIGILL, SIGSEGV etc.). */ set_fatal_code(); if (argc > 1) { /* The first file opened does not need a NEWDOC_A action. Note that file loading can be interrupted (wildcarding can sometimes produce unwanted results). */ uint64_t first_line = 0, first_col = 0; bool binary = false, skip_plus = false, read_only = false; stop = false; for(int i = 1; i < argc && !stop; i++) { if (argv[i] && !skiplist[i]) { if (argv[i][0] == '+' && !skip_plus) { /* looking for "+", or "+N" or "+N,M" */ uint64_t tmp_l = INT64_MAX, tmp_c = 0; char *d; errno = 0; if (argv[i][1]) { if (isdigit((unsigned char)argv[i][1])) { tmp_l = strtoll(argv[i]+1, &d, 10); if (!errno) { if (*d) { /* separator between N and M */ if (isdigit((unsigned char)d[1])) { tmp_c = strtoll(d+1, &d, 10); if (*d) errno = ERANGE; } else errno = ERANGE; } } } else errno = ERANGE; } if (!errno) { first_line = tmp_l; first_col = tmp_c; } else { skip_plus = true; i--; } } else if (!strcmp(argv[i], "--binary")) { binary = true; } else if (!strcmp(argv[i], "--read-only") || !strcmp(argv[i], "--readonly") || !strcmp(argv[i], "--ro")) { read_only = true; } else { if (!strcmp(argv[i], "-") && stdin_buffer) { stdin_buffer->opt.binary = binary; if (read_only) stdin_buffer->opt.read_only = read_only; if (first_line) do_action(stdin_buffer, GOTOLINE_A, first_line, NULL); if (first_col) do_action(stdin_buffer, GOTOCOLUMN_A, first_col, NULL); stdin_buffer = NULL; } else { if (!strcmp(argv[i], "--")) i++; if (!first_file) do_action(cur_buffer, NEWDOC_A, -1, NULL); else first_file = false; cur_buffer->opt.binary = binary; if (i < argc) do_action(cur_buffer, OPEN_A, 0, str_dup(argv[i])); if (first_line) do_action(cur_buffer, GOTOLINE_A, first_line, NULL); if (first_col) do_action(cur_buffer, GOTOCOLUMN_A, first_col, NULL); if (read_only) cur_buffer->opt.read_only = read_only; } first_line = first_col = 0; skip_plus = binary = read_only = false; } } } free(skiplist); /* This call makes current the first specified file. It is called only if more than one buffer exist. */ if (get_nth_buffer(1)) do_action(cur_buffer, NEXTDOC_A, -1, NULL); } /* We delay updates. In this way the macro activity does not cause display activity. */ reset_window(); delay_update(); if (macro_name) do_action(cur_buffer, MACRO_A, -1, str_dup(macro_name)); else if (first_file) { /* If there is no file to load, and no macro to execute, we display the "NO WARRANTY" message. */ about(); } while(true) { /* If we are displaying the "NO WARRANTY" info, we should not refresh the window now */ if (!displaying_info) { refresh_window(cur_buffer); if (cur_buffer->opt.automatch) automatch_bracket(cur_buffer, true); } draw_status_bar(); move_cursor(cur_buffer->cur_y, cur_buffer->cur_x); int c = get_key_code(); if (window_changed_size) { print_error(do_action(cur_buffer, REFRESH_A, 0, NULL)); window_changed_size = displaying_info = false; cur_buffer->automatch.shown = 0; } if (c == INVALID_CHAR) continue; /* Window resizing. */ const input_class ic = CHAR_CLASS(c); if (displaying_info) { refresh_window(cur_buffer); displaying_info = false; } if (cur_buffer->automatch.shown) automatch_bracket(cur_buffer, false); switch(ic) { case INVALID: print_error(INVALID_CHARACTER); break; case ALPHA: print_error(do_action(cur_buffer, INSERTCHAR_A, c, NULL)); break; case TAB: print_error(do_action(cur_buffer, INSERTTAB_A, 1, NULL)); break; case RETURN: print_error(do_action(cur_buffer, INSERTLINE_A, -1, NULL)); break; case COMMAND: if (c < 0) c = -c - 1; if (key_binding[c]) print_error(execute_command_line(cur_buffer, key_binding[c])); break; default: break; } } }