/* vi: set sw=4 ts=4: */ /* * nanobox:A small 'nano' clone * Copyright (C) 2000, 2001 Sterling Huxley * Copyright (C) 2019 Michele Bini * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ /* * Things To Do: * add help command * if mark[] values were line numbers rather than pointers * it would be easier to change the mark when add/delete lines * More intelligence in refresh() * filter text through an external command * An "ex" line oriented mode- maybe using "cmdedit" */ #define ENABLE_FEATURE_NANO 1 //config:config NANO //config: bool "nano" //config: default y //config: help //config: 'nano' is a text editor. This is a clone attempt. //config: #define CONFIG_FEATURE_NANO_MAX_LEN 4096 //config:config FEATURE_NANO_MAX_LEN //config: int "Maximum screen width" //config: range 256 16384 //config: default 4096 //config: depends on NANO //config: help //config: Contrary to what you may think, this is not eating much. //config: Make it smaller than 4k only if you are very limited on memory. //config: #define ENABLE_FEATURE_NANO_8BIT 1 //config:config FEATURE_NANO_8BIT //config: bool "Allow to display 8-bit chars (otherwise shows dots)" //config: default n //config: depends on NANO //config: help //config: If your terminal can display characters with high bit set, //config: you may want to enable this. Note: nano is not Unicode-capable. //config: If your terminal combines several 8-bit bytes into one character //config: (as in Unicode mode), this will not work properly. //config: #define ENABLE_FEATURE_NANO_UTF8 1 //config:config FEATURE_NANO_UTF8 //config: bool "Allow editing utf-8 files" //config: default y //config: depends on NANO //config: help //config: Allow input of utf8 characters. (EXPERIMENTAL) //config: #define ENABLE_FEATURE_NANO_UTF8_MIXED 1 //config:config FEATURE_NANO_UTF8_MIXED //config: bool "Allow editing of utf-8 files containing invalid character sequences" //config: default y //config: depends on NANO_UTF8 //config: help //config: Allow input of utf8 characters. (EXPERIMENTAL) //config: #define ENABLE_FEATURE_NANO_YANKMARK 1 //config:config FEATURE_NANO_YANKMARK //config: bool "Enable yank/put commands and mark cmds" //config: default y //config: depends on NANO //config: help //config: This will enable you to use yank and put, as well as mark. //config: #define ENABLE_FEATURE_NANO_SEARCH 1 #define IF_FEATURE_NANO_SEARCH(...) __VA_ARGS__ //config:config FEATURE_NANO_SEARCH //config: bool "Enable search and replace cmds" //config: default y //config: depends on NANO //config: help //config: Select this if you wish to be able to do search and replace. //config: #define ENABLE_FEATURE_NANO_REGEX_SEARCH 0 //config:config FEATURE_NANO_REGEX_SEARCH //config: bool "Enable regex in search and replace" //config: default n # Uses GNU regex, which may be unavailable. FIXME //config: depends on FEATURE_NANO_SEARCH //config: help //config: Use extended regex search. //config: #define ENABLE_FEATURE_NANO_USE_SIGNALS 1 //config:config FEATURE_NANO_USE_SIGNALS //config: bool "Catch signals" //config: default y //config: depends on NANO //config: help //config: Selecting this option will make nano signal aware. This will support //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms. //config: #define ENABLE_FEATURE_NANO_READONLY 1 //config:config FEATURE_NANO_READONLY //config: bool "Enable -R option and \"view\" mode" //config: default y //config: depends on NANO //config: help //config: Enable the read-only command line option, which allows the user to //config: open a file in read-only mode. //config: #define ENABLE_FEATURE_NANO_SETOPTS 1 //config:config FEATURE_NANO_SETOPTS //config: bool "Enable settable options, ai ic showmatch" //config: default y //config: depends on NANO //config: help //config: Enable the editor to set some (ai, ic, showmatch) options. //config: Consider removing this option! FIXME //config: #define ENABLE_FEATURE_NANO_WIN_RESIZE 1 //config:config FEATURE_NANO_WIN_RESIZE //config: bool "Handle window resize" //config: default y //config: depends on NANO //config: help //config: Behave nicely with terminals that get resized. //config: #define ENABLE_FEATURE_NANO_ASK_TERMINAL 1 #define IF_FEATURE_NANO_ASK_TERMINAL(...) __VA_ARGS__ //config:config FEATURE_NANO_ASK_TERMINAL //config: bool "Use 'tell me cursor position' ESC sequence to measure window" //config: default y //config: depends on NANO //config: help //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set, //config: this option makes nano perform a last-ditch effort to find it: //config: position cursor to 999,999 and ask terminal to report real //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin. //config: This is not clean but helps a lot on serial lines and such. //config: #define ENABLE_FEATURE_NANO_UNDO 1 //config:config FEATURE_NANO_UNDO //config: bool "Support undo command \"u\"" //config: default y //config: depends on NANO //config: help //config: Support the 'u' command to undo insertion, deletion, and replacement //config: of text. //config: #define ENABLE_FEATURE_NANO_UNDO_QUEUE 1 //config:config FEATURE_NANO_UNDO_QUEUE //config: bool "Enable undo operation queuing" //config: default y //config: depends on FEATURE_NANO_UNDO //config: help //config: The nano undo functions can use an intermediate queue to greatly lower //config: malloc() calls and overhead. When the maximum size of this queue is //config: reached, the contents of the queue are committed to the undo stack. //config: This increases the size of the undo code and allows some undo //config: operations (especially un-typing/backspacing) to be far more useful. //config: #define CONFIG_FEATURE_NANO_UNDO_QUEUE_MAX 256 //config:config FEATURE_NANO_UNDO_QUEUE_MAX //config: int "Maximum undo character queue size" //config: default 256 //config: range 32 65536 //config: depends on FEATURE_NANO_UNDO_QUEUE //config: help //config: This option sets the number of bytes used at runtime for the queue. //config: Smaller values will create more undo objects and reduce the amount //config: of typed or backspaced characters that are grouped into one undo //config: operation; larger values increase the potential size of each undo //config: and will generally malloc() larger objects and less frequently. //config: Unless you want more (or less) frequent "undo points" while typing, //config: you should probably leave this unchanged. //config: // #define ENABLE_FEATURE_NANO_INDENT_SENSITIVE 1 //config:config FEATURE_NANO_INDENT_SENSITIVE //config: bool "Make common operations indent-sensitive" //config: default y //config: depends on NANO //config: help //config: This option makes the HOME and RETURN key behave differently //config: depending on the current indentation level. //config: #define ENABLE_FEATURE_NANO_DEVTTY 1 #define ENABLE_FEATURE_NANO_STDIO 1 //config:config FEATURE_NANO_STDIO_C //config: bool "Support -c flag for operating on standard input/output" //config: default y //config: depends on NANO, NANO_DEVTTY //config: help //config: Support -c flags for reading a file on standard input, and //config: writing it back on standard output. //config: // #define ENABLE_FEATURE_NANO_FOLDING 1 //config:config FEATURE_NANO_FOLDING //config: bool "Support code folding" //config: default n //config: depends on NANO //config: help //config: Support code folding. //config: NOT IMPLEMENTED YET //config: #define ENABLE_FEATURE_NANO_HIGHLIGHT 1 //config:config FEATURE_NANO_HIGHLIGHT //config: bool "Support syntax highlighting" //config: default y //config: depends on NANO //config: help //config: Support syntax highlighting. //config: NOT IMPLEMENTED YET //config: #define ENABLE_FEATURE_NANO_HIGHLIGHT_LANGUAGE_C 1 //config:config FEATURE_NANO_HIGHLIGHT_LANGUAGE_C //config: bool "Support syntax highlighting for C language" //config: default y //config: depends on NANO //config: help //config: Support syntax highlighting for the C language //config: NOT IMPLEMENTED YET //config: #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 1 #include "platform.h" #define FALSE 0 #define TRUE 1 #define smallint int // #define KEYCODE_BUFFER_SIZE 16 struct globals *ptr_to_globals; #define SET_PTR_TO_GLOBALS(x) ptr_to_globals = (x) #define xzalloc malloc #define xstrndup strndup #define xstrdup strdup #define xmalloc malloc #define xrealloc realloc #define xfree free #define FAST_FUNC int FAST_FUNC get_terminal_width_height(int fd, unsigned *width, unsigned *height); int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout); #include "keycodes.h" #define NOINLINE #define ARRAY_SIZE(x) ((unsigned)((sizeof(x)) / (sizeof(x[0])))) int FAST_FUNC set_termios_to_raw(int fd, struct termios *oldterm, int flags); #define TERMIOS_RAW_CRNL (1 << 1) #define TERMIOS_CLEAR_ISIG (1 << 0) #define TERMIOS_RAW_INPUT (1 << 2) int FAST_FUNC tcsetattr_stdin_TCSANOW(const struct termios *tp); int safe_poll(struct pollfd *ufds, nfds_t nfds, int timeout_ms) FAST_FUNC; void FAST_FUNC bb_error_msg_and_die(const char *s, ...); ssize_t FAST_FUNC full_read(int fd, void *buf, size_t len); ssize_t FAST_FUNC full_write(int fd, const void *buf, size_t len); static int wh_helper(int value, int def_val, const char *env_name, int *err); #endif //applet:IF_NANO(APPLET(nano, BB_DIR_BIN, BB_SUID_DROP)) //kbuild:lib-$(CONFIG_NANO) += nano.o //usage:#define nano_trivial_usage //usage: "[OPTIONS] [FILE]..." //usage:#define nano_full_usage "\n\n" //usage: "Edit FILE\n" //usage: IF_FEATURE_NANO_READONLY( //usage: "\n -R Read-only" //usage: ) //usage: "\n -H List available features" // #include "libbb.h" /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */ #if ENABLE_FEATURE_NANO_REGEX_SEARCH # include #endif /* the CRASHME code is unmaintained, and doesn't currently build */ #define ENABLE_FEATURE_NANO_CRASHME 0 #if ENABLE_LOCALE_SUPPORT #if ENABLE_FEATURE_NANO_8BIT //FIXME: this does not work properly for Unicode anyway # define Isprint(c) (isprint)(c) #else # define Isprint(c) isprint_asciionly(c) #endif #else /* 0x9b is Meta-ESC */ #if ENABLE_FEATURE_NANO_8BIT # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b) #else # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f) #endif #endif enum { MAX_TABSTOP = 32, // sanity limit // User input len. Need not be extra big. // Lines in file being edited *can* be bigger than this. MAX_INPUT_LEN = 128, // Sanity limits. We have only one buffer of this size. MAX_SCR_COLS = CONFIG_FEATURE_NANO_MAX_LEN, MAX_SCR_ROWS = CONFIG_FEATURE_NANO_MAX_LEN, }; /* VT102 ESC sequences. * See "Xterm Control Sequences" * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html */ /* Inverse/Normal text */ #define ESC_BOLD_TEXT "\033[7m" #define ESC_NORM_TEXT "\033[0m" /* Bell */ #define ESC_BELL "\007" /* Clear-to-end-of-line */ #define ESC_CLEAR2EOL "\033[K" /* Clear-to-end-of-screen. * (We use default param here. * Full sequence is "ESC [ J", * is 0/1/2 = "erase below/above/all".) */ #define ESC_CLEAR2EOS "\033[J" /* Cursor to given coordinate (1,1: top left) */ #define ESC_SET_CURSOR_POS "\033[%u;%uH" //UNUSED // /* Cursor up and down */ //#define ESC_CURSOR_UP "\033[A" //#define ESC_CURSOR_DOWN "\n" /* nano.c expects chars to be unsigned. */ /* busybox build system provides that, but it's better */ /* to audit and fix the source */ struct globals { /* many references - keep near the top of globals */ char *text, *end; // pointers to the user data in memory char *dot; // where all the action takes place int text_size; // size of the allocated buffer /* the rest */ smallint nano_setops; #define NANO_AUTOINDENT 1 #define NANO_SHOWMATCH 2 #define NANO_IGNORECASE 4 #define NANO_ERR_METHOD 8 #define autoindent (nano_setops & NANO_AUTOINDENT) #define showmatch (nano_setops & NANO_SHOWMATCH ) #define ignorecase (nano_setops & NANO_IGNORECASE) #if ENABLE_FEATURE_NANO_READONLY smallint readonly_mode; #define SET_READONLY_FILE(flags) ((flags) |= 0x01) #define SET_READONLY_MODE(flags) ((flags) |= 0x02) #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe) #else #define SET_READONLY_FILE(flags) ((void)0) #define SET_READONLY_MODE(flags) ((void)0) #define UNSET_READONLY_FILE(flags) ((void)0) #endif smallint editing; // >0 while we are editing a file // [code audit says "can be 0, 1 or 2 only"] int modified_count; // buffer contents changed if !0 int last_modified_count; // = -1; int save_argc; // how many file names on cmd line unsigned rows, columns; // the terminal screen is this size #if ENABLE_FEATURE_NANO_ASK_TERMINAL int get_rowcol_error; #endif int crow, ccol; // cursor is on Crow x Ccol int offset; // chars scrolled off the screen to the left int have_status_msg; // is default edit status needed? // [don't make smallint!] int last_status_cksum; // hash of current status line char *current_filename; char *screenbegin; // index into text[], of top line on the screen char *screen; // pointer to the virtual screen buffer int screensize; // and its size int tabstop; int last_forward_char; // last char searched for with 'f' (int because of Unicode) char erase_char; // the users erase character char last_input_char; // last char read from user #if ENABLE_FEATURE_NANO_USE_SIGNALS || ENABLE_FEATURE_NANO_CRASHME int my_pid; #endif #if ENABLE_FEATURE_NANO_SEARCH char *last_search_pattern; // last pattern from a '/' or '?' search #endif /* former statics */ #if ENABLE_FEATURE_NANO_YANKMARK char *edit_file__cur_line; #endif int refresh__old_offset; int format_edit_status__tot; /* a few references only */ #if ENABLE_FEATURE_NANO_YANKMARK // char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27 char *clipboard; char *mark[28]; // user marks points somewhere in text[]- a-z and previous context '' char *context_start, *context_end; #endif #if ENABLE_FEATURE_NANO_USE_SIGNALS sigjmp_buf restart; // catch_sig() #endif struct termios term_orig; // remember what the cooked mode was // Should be just enough to hold a key sequence, // but CRASHME mode uses it as generated command buffer too #if ENABLE_FEATURE_NANO_CRASHME char readbuffer[128]; #else char readbuffer[KEYCODE_BUFFER_SIZE]; #endif #define STATUS_BUFFER_LEN 200 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user char get_input_line__buf[MAX_INPUT_LEN]; char search__buf[MAX_INPUT_LEN]; char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2]; #if ENABLE_FEATURE_NANO_UNDO // undo_push() operations #define UNDO_INS 0 #define UNDO_DEL 1 #define UNDO_INS_CHAIN 2 #define UNDO_DEL_CHAIN 3 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG #define UNDO_QUEUED_FLAG 4 #define UNDO_INS_QUEUED 4 #define UNDO_DEL_QUEUED 5 #define UNDO_USE_SPOS 32 #define UNDO_EMPTY 64 // Pass-through flags for functions that can be undone #define NO_UNDO 0 #define ALLOW_UNDO 1 #define ALLOW_UNDO_CHAIN 2 # if ENABLE_FEATURE_NANO_UNDO_QUEUE #define ALLOW_UNDO_QUEUED 3 char undo_queue_state; int undo_q; char *undo_queue_spos; // Start position of queued operation char undo_queue[CONFIG_FEATURE_NANO_UNDO_QUEUE_MAX]; # else // If undo queuing disabled, don't invoke the missing queue logic #define ALLOW_UNDO_QUEUED 1 # endif struct undo_object { struct undo_object *prev; // Linking back avoids list traversal (LIFO) int start; // Offset where the data should be restored/deleted int length; // total data size uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped char undo_text[1]; // text that was deleted (if deletion) } *undo_stack_tail; #endif /* ENABLE_FEATURE_NANO_UNDO */ #if ENABLE_FEATURE_NANO_FOLDING char *current_fold; #endif }; #define G (*ptr_to_globals) #define text (G.text ) #define text_size (G.text_size ) #define end (G.end ) #define dot (G.dot ) #define clipboard (G.clipboard ) #define nano_setops (G.nano_setops ) #define editing (G.editing ) #define modified_count (G.modified_count ) #define last_modified_count (G.last_modified_count) #define save_argc (G.save_argc ) #define rows (G.rows ) #define columns (G.columns ) #define crow (G.crow ) #define ccol (G.ccol ) #define offset (G.offset ) #define status_buffer (G.status_buffer ) #define have_status_msg (G.have_status_msg ) #define last_status_cksum (G.last_status_cksum ) #define current_filename (G.current_filename ) #define screen (G.screen ) #define screensize (G.screensize ) #define screenbegin (G.screenbegin ) #define tabstop (G.tabstop ) #define last_forward_char (G.last_forward_char ) #define erase_char (G.erase_char ) #define last_input_char (G.last_input_char ) #if ENABLE_FEATURE_NANO_READONLY #define readonly_mode (G.readonly_mode ) #else #define readonly_mode 0 #endif #define adding2q (G.adding2q ) #define lmc_len (G.lmc_len ) #define ioq (G.ioq ) #define ioq_start (G.ioq_start ) #define my_pid (G.my_pid ) #define last_search_pattern (G.last_search_pattern) #define edit_file__cur_line (G.edit_file__cur_line) #define refresh__old_offset (G.refresh__old_offset) #define format_edit_status__tot (G.format_edit_status__tot) #define mark (G.mark ) #define context_start (G.context_start ) #define context_end (G.context_end ) #define restart (G.restart ) #define term_orig (G.term_orig ) #define initial_cmds (G.initial_cmds ) #define readbuffer (G.readbuffer ) #define scr_out_buf (G.scr_out_buf ) #define last_modifying_cmd (G.last_modifying_cmd ) #define get_input_line__buf (G.get_input_line__buf) #define search__buf (G.search__buf) #if ENABLE_FEATURE_NANO_UNDO #define undo_stack_tail (G.undo_stack_tail ) # if ENABLE_FEATURE_NANO_UNDO_QUEUE #define undo_queue_state (G.undo_queue_state) #define undo_q (G.undo_q ) #define undo_queue (G.undo_queue ) #define undo_queue_spos (G.undo_queue_spos ) # endif #endif #if ENABLE_FEATURE_NANO_FOLDING #define current_fold (G.current_fold) #endif #define INIT_G() do { \ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ last_modified_count = -1; \ /* "" but has space for 2 chars: */ \ IF_FEATURE_NANO_SEARCH(last_search_pattern = xzalloc(2);) \ } while (0) static void edit_file(char *); // edit one file static void do_cmd(int); // execute a command static int next_tabstop(int); static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot static char *begin_line(char *); // return pointer to cur line B-o-l static char *end_line(char *); // return pointer to cur line E-o-l static char *prev_line(char *); // return pointer to prev line B-o-l static char *next_line(char *); // return pointer to next line B-o-l static char *end_screen(void); // get pointer to last char on screen static int count_lines(char *, char *); // count line from start to stop static char *move_to_col(char *, int); // move "p" to column l static void dot_left(void); // move dot left- dont leave line static void dot_right(void); // move dot right- dont leave line static void dot_begin(void); // move dot to B-o-l static void dot_next(void); // move dot to next line B-o-l static void dot_prev(void); // move dot to prev line B-o-l static void dot_scroll(int, int); // move the screen up or down static void dot_skip_over_ws(void); // move dot pat WS static char *bound_dot(char *); // make sure text[0] <= P < "end" static char *new_screen(int, int); // malloc virtual screen memory #if !ENABLE_FEATURE_NANO_UNDO #define char_insert(a,b,c) char_insert(a,b) #endif static char *char_insert(char *, char, int); // insert the char c at 'p' // might reallocate text[]! use p += stupid_insert(p, ...), // and be careful to not use pointers into potentially freed text[]! static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' static char *find_pair(char *, char); // find matching pair () [] {} #if !ENABLE_FEATURE_NANO_UNDO #define text_hole_delete(a,b,c) text_hole_delete(a,b) #endif static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole // might reallocate text[]! use p += text_hole_make(p, ...), // and be careful to not use pointers into potentially freed text[]! static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole #if !ENABLE_FEATURE_NANO_UNDO #define yank_delete(a,b,c,d) yank_delete(a,b,c) #endif static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete static void show_help(void); // display some help info static void rawmode(void); // set "raw" mode on tty static void cookmode(void); // return to "cooked" mode on tty // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready) static int mysleep(int); static int get_one_char(void); // read 1 char from stdin // file_insert might reallocate text[]! static int file_insert(const char *, char *, int); static int file_write(char *, char *, char *); static void place_cursor(int, int); static void screen_erase(void); static void clear_to_eol(void); static void clear_to_eos(void); static void go_bottom_and_clear_to_eol(void); static void standout_start(void); // send "start reverse video" sequence static void standout_end(void); // send "end reverse video" sequence static void show_status_line(void); // put a message on the bottom line static void status_line(const char *, ...); // print to status buf static void status_line_bold(const char *, ...); static void status_line_bold_errno(const char *fn); static void not_implemented(const char *); // display "Not implemented" message static int format_edit_status(void); // format file status on status line static void redraw(int); // force a full screen refresh static char* format_line(char* /*, int*/); static void refresh(int); // update the terminal from screen[] static void indicate_error(void); // beep to indicate error static void Hit_Return(void); #if ENABLE_FEATURE_NANO_USE_SIGNALS static void winch_sig(int); // catch window size changes static void suspend_sig(int); // catch ctrl-Z static void catch_sig(int); // catch ctrl-C and alarm time-outs #endif #define end_cmd_q() ((void)0) #if ENABLE_FEATURE_NANO_SETOPTS static void showmatching(char *); // show the matching pair () [] {} #endif #if ENABLE_FEATURE_NANO_STDIO int nano_stdio_mode = 0; #endif #if ENABLE_FEATURE_NANO_YANKMARK || ENABLE_FEATURE_NANO_SEARCH || ENABLE_FEATURE_NANO_CRASHME // might reallocate text[]! use p += string_insert(p, ...), // and be careful to not use pointers into potentially freed text[]! # if !ENABLE_FEATURE_NANO_UNDO #define string_insert(a,b,c) string_insert(a,b) # endif static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p' #endif #if ENABLE_FEATURE_NANO_YANKMARK static char *text_yank(char *, char *); // save copy of "p" into a register static int yank_append = 0; static int previous_yank_position = -1; #endif #if ENABLE_FEATURE_NANO_UNDO static void flush_undo_data(void); static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack static void undo_pop(void); // Undo the last operation # if ENABLE_FEATURE_NANO_UNDO_QUEUE static void undo_queue_commit(void); // Flush any queued objects to the undo stack # else # define undo_queue_commit() ((void)0) # endif #else #define flush_undo_data() ((void)0) #define undo_queue_commit() ((void)0) #endif #if ENABLE_FEATURE_NANO_CRASHME static void crash_dummy(); static void crash_test(); static int crashme = 0; #endif #if ENABLE_FEATURE_NANO_HIGHLIGHT #endif #if ENABLE_FEATURE_NANO_DEVTTY FILE *devtty; int nano_stdin_fd; # define NANO_STDOUT devtty # define NANO_STDIN_FD nano_stdin_fd #else # define NANO_STDOUT stdout # define NANO_STDIN_FD STDIN_FILENO #endif static void write_char(const char c) { fputc(c, NANO_STDOUT); } static void write1(const char *out) { fputs(out, NANO_STDOUT); } int main(int argc, char **argv) { int c; INIT_G(); #if ENABLE_FEATURE_NANO_UNDO /* undo_stack_tail = NULL; - already is */ #if ENABLE_FEATURE_NANO_UNDO_QUEUE undo_queue_state = UNDO_EMPTY; /* undo_q = 0; - already is */ #endif #endif #if ENABLE_FEATURE_NANO_USE_SIGNALS || ENABLE_FEATURE_NANO_CRASHME my_pid = getpid(); #endif #if ENABLE_FEATURE_NANO_CRASHME srand((long) my_pid); #endif #ifdef NO_SUCH_APPLET_YET /* If we aren't "nano", we are "view" */ if (ENABLE_FEATURE_NANO_READONLY && applet_name[2]) { SET_READONLY_MODE(readonly_mode); } #endif // autoindent is not default in vi 7.3 nano_setops = /*NANO_AUTOINDENT |*/ NANO_SHOWMATCH | NANO_IGNORECASE; // 1- process $HOME/.exrc file (not inplemented yet) // 2- process EXINIT variable from environment // 3- process command line args while ((c = getopt(argc, argv, "hcCRH")) != -1) { switch (c) { #if ENABLE_FEATURE_NANO_CRASHME case 'C': crashme = 1; break; #endif #if ENABLE_FEATURE_NANO_READONLY case 'R': // Read-only flag SET_READONLY_MODE(readonly_mode); break; #endif #if ENABLE_FEATURE_NANO_STDIO case 'c': nano_stdio_mode = 1; break; #endif case 'H': show_help(); /* fall through */ default: // bb_show_usage(); return 1; } } // The argv array can be used by the ":next" and ":rewind" commands // FIXME: this functionality could be made optional (multi-file editing) and is currently not accessible anyway! argv += optind; argc -= optind; //----- This is the main file handling loop -------------- save_argc = argc; optind = 0; // "Save cursor, use alternate screen buffer, clear screen" #if ENABLE_FEATURE_NANO_DEVTTY devtty = fopen("/dev/tty", "a+"); nano_stdin_fd = fileno(devtty); #endif write1("\033[?1049h"); #if ENABLE_FEATURE_NANO_STDIO if (nano_stdio_mode) { edit_file(NULL); } else { #endif while (1) { edit_file(argv[optind]); /* param might be NULL */ if (++optind >= argc) break; } #if ENABLE_FEATURE_NANO_STDIO } #endif // "Use normal screen buffer, restore cursor" write1("\033[?1049l"); //----------------------------------------------------------- return 0; } /* read text from file or create an empty buf */ /* will also update current_filename */ static int init_text_buffer(char *fn) { int rc; /* allocate/reallocate text buffer */ free(text); text_size = 10240; screenbegin = dot = end = text = xzalloc(text_size); #if ENABLE_FEATURE_NANO_STDIO if (!nano_stdio_mode) { #endif if (fn != current_filename) { free(current_filename); current_filename = xstrdup(fn); } #if ENABLE_FEATURE_NANO_STDIO } #endif rc = file_insert(fn, text, 1); if (rc < 0) { // file doesnt exist. Start empty buf with dummy line char_insert(text, '\n', NO_UNDO); } flush_undo_data(); modified_count = 0; last_modified_count = -1; #if ENABLE_FEATURE_NANO_YANKMARK /* init the marks */ memset(mark, 0, sizeof(mark)); #endif return rc; } #if ENABLE_FEATURE_NANO_WIN_RESIZE static int query_screen_dimensions(void) { int err = get_terminal_width_height(NANO_STDIN_FD, &columns, &rows); if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS; if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS; return err; } #else # define query_screen_dimensions() (0) #endif static void edit_file(char *fn) { #if ENABLE_FEATURE_NANO_YANKMARK #define cur_line edit_file__cur_line #endif int c; #if ENABLE_FEATURE_NANO_USE_SIGNALS int sig; #endif editing = 1; // 0 = exit, 1 = one file, 2 = multiple files rawmode(); rows = 24; columns = 80; IF_FEATURE_NANO_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions(); #if ENABLE_FEATURE_NANO_ASK_TERMINAL if (G.get_rowcol_error /* TODO? && no input on stdin */) { uint64_t k; write1("\033[999;999H" "\033[6n"); fflush(NULL); k = read_key(NANO_STDIN_FD, readbuffer, /*timeout_ms:*/ 100); if ((int32_t)k == KEYCODE_CURSOR_POS) { uint32_t rc = (k >> 32); columns = (rc & 0x7fff); if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS; rows = ((rc >> 16) & 0x7fff); if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS; } } #endif new_screen(rows, columns); // get memory for virtual screen init_text_buffer(fn); #if ENABLE_FEATURE_NANO_YANKMARK mark[26] = mark[27] = text; // init "previous context" #endif last_forward_char = last_input_char = '\0'; crow = 0; ccol = 0; #if ENABLE_FEATURE_NANO_USE_SIGNALS signal(SIGINT, catch_sig); signal(SIGWINCH, winch_sig); signal(SIGTSTP, suspend_sig); sig = sigsetjmp(restart, 1); if (sig != 0) { screenbegin = dot = text; } #endif tabstop = 8; offset = 0; // no horizontal offset c = '\0'; redraw(FALSE); // dont force every col re-draw //------This is the main Nano cmd handling loop ----------------------- while (editing > 0) { #if ENABLE_FEATURE_NANO_CRASHME if (crashme > 0) { if ((end - text) > 1) { crash_dummy(); // generate a random command } else { crashme = 0; string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string dot = text; refresh(FALSE); } } #endif last_input_char = c = get_one_char(); // get a cmd from user do_cmd(c); // execute the user command // poll to see if there is input already waiting. if we are // not able to display output fast enough to keep up, skip // the display update until we catch up with input. if (!readbuffer[0] && mysleep(0) == 0) { // no input pending - so update output refresh(FALSE); show_status_line(); } #if ENABLE_FEATURE_NANO_CRASHME if (crashme > 0) crash_test(); // test editor variables #endif } //------------------------------------------------------------------- go_bottom_and_clear_to_eol(); cookmode(); #undef cur_line } static void Hit_Return(void) { int c; standout_start(); write1("[Hit return to continue]"); standout_end(); while ((c = get_one_char()) != '\n' && c != '\r') continue; redraw(TRUE); // force redraw all } static int next_tabstop(int col) { return col + ((tabstop - 1) - (col % tabstop)); } #if ENABLE_FEATURE_NANO_UTF8 static int utf8_char_len(unsigned char c) { return (c & 0x40) ? ((c & 0x20) ? ((c & 0x10) ? ((c & 0x8) ? 1 : 4) : 3) : 2) : 1; } #endif //----- Synchronize the cursor to Dot -------------------------- static NOINLINE void sync_cursor(char *d, int *row, int *col) { char *beg_cur; // begin and end of "d" line char *tp; int cnt, ro, co; beg_cur = begin_line(d); // first char of cur line if (beg_cur < screenbegin) { // "d" is before top line on screen // how many lines do we have to move cnt = count_lines(beg_cur, screenbegin); sc1: screenbegin = beg_cur; if (cnt > (rows - 1) / 2) { // we moved too many lines. put "dot" in middle of screen for (cnt = 0; cnt < (rows - 1) / 2; cnt++) { screenbegin = prev_line(screenbegin); } } } else { char *end_scr; // begin and end of screen end_scr = end_screen(); // last char of screen if (beg_cur > end_scr) { // "d" is after bottom line on screen // how many lines do we have to move cnt = count_lines(end_scr, beg_cur); if (cnt > (rows - 1) / 2) goto sc1; // too many lines for (ro = 0; ro < cnt - 1; ro++) { // move screen begin the same amount screenbegin = next_line(screenbegin); // now, move the end of screen end_scr = next_line(end_scr); end_scr = end_line(end_scr); } } } // "d" is on screen- find out which row tp = screenbegin; for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row if (tp == beg_cur) break; tp = next_line(tp); } // find out what col "d" is on co = 0; while (tp < d) { // drive "co" to correct column if (*tp == '\n') //vda || *tp == '\0') break; if (*tp == '\t') { // handle tabs like real nano if (d == tp) { break; } co = next_tabstop(co); } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) { co++; // display as ^X, use 2 columns #if ENABLE_FEATURE_NANO_UTF8 } else if (*tp & 0x80) { #if ENABLE_FEATURE_NANO_UTF8_MIXED char *tp_orig = tp; int char_len = utf8_char_len(*tp) - 1; do { tp++; if ((*tp & 0xC0) != 0x80) { tp = tp_orig; break; } if (!(--char_len > 0)) break; if (!(tp < d)) { tp = tp_orig; break; } } while (1); #else int char_len = utf8_char_len(*tp) - 1; do { tp++; } while (tp < d && (--char_len > 0)); #endif #endif } co++; tp++; } // "co" is the column where "dot" is. // The screen has "columns" columns. // The currently displayed columns are 0+offset -- columns+ofset // |-------------------------------------------------------------| // ^ ^ ^ // offset | |------- columns ----------------| // // If "co" is already in this range then we do not have to adjust offset // but, we do have to subtract the "offset" bias from "co". // If "co" is outside this range then we have to change "offset". // If the first char of a line is a tab the cursor will try to stay // in column 7, but we have to set offset to 0. if (co < 0 + offset) { offset = co; } if (co >= columns + offset) { offset = co - columns + 1; } // if the first char of the line is a tab, and "dot" is sitting on it // force offset to 0. if (d == beg_cur && *d == '\t') { offset = 0; } co -= offset; *row = ro; *col = co; } //----- Text Movement Routines --------------------------------- static char *begin_line(char *p) // return pointer to first char cur line { if (p > text) { p = memrchr(text, '\n', p - text); if (!p) return text; return p + 1; } return p; } static char *end_line(char *p) // return pointer to NL of cur line { if (p < end - 1) { p = memchr(p, '\n', end - p - 1); if (!p) return end - 1; } return p; } static char *prev_line(char *p) // return pointer first char prev line { p = begin_line(p); // goto beginning of cur line if (p > text && p[-1] == '\n') p--; // step to prev line p = begin_line(p); // goto beginning of prev line return p; } static char *next_line(char *p) // return pointer first char next line { p = end_line(p); if (p < end - 1 && *p == '\n') p++; // step to next line return p; } //----- Text Information Routines ------------------------------ static char *end_screen(void) { char *q; int cnt; // find new bottom line q = screenbegin; for (cnt = 0; cnt < rows - 2; cnt++) q = next_line(q); q = end_line(q); return q; } // count line from start to stop static int count_lines(char *start, char *stop) { char *q; int cnt; if (stop < start) { // start and stop are backwards- reverse them q = start; start = stop; stop = q; } cnt = 0; stop = end_line(stop); while (start <= stop && start <= end - 1) { start = end_line(start); if (*start == '\n') cnt++; start++; } return cnt; } //----- Dot Movement Routines ---------------------------------- static void dot_left(void) { undo_queue_commit(); #if 0 // Vi-like movement if (dot > text && dot[-1] != '\n') dot--; #else if (dot > text) dot--; #if ENABLE_FEATURE_NANO_UTF8 if (*dot & 0x80) { char *utf8_char_start = dot; #if ENABLE_FEATURE_NANO_UTF8_MIXED do { if (*utf8_char_start & 0x40) break; if (utf8_char_start <= text) goto unrecognized_utf8_code; utf8_char_start--; if (!(*utf8_char_start & 0x80)) goto unrecognized_utf8_code; if (dot - utf8_char_start > 3) goto unrecognized_utf8_code; } while (1); if (utf8_char_len(*utf8_char_start) == (dot - utf8_char_start + 1)) { dot = utf8_char_start; } #else do { if (*utf8_char_start & 0x40) break; if (utf8_char_start <= text) goto unrecognized_utf8_code; utf8_char_start--; } while (1); dot = utf8_char_start; #endif unrecognized_utf8_code: ; } #endif #endif } static void dot_right(void) { undo_queue_commit(); #if 0 // Vi-like movement if (dot < end - 1 && *dot != '\n') dot++; #else #if ENABLE_FEATURE_NANO_UTF8 if (*dot & 0x80) { #if ENABLE_FEATURE_NANO_UTF8_MIXED char *orig_dot = dot; int char_len = utf8_char_len(*dot); while (dot < end - 1 && --char_len > 0) { dot++; if (*dot & 0xC0 != 0x80) { dot = orig_dot; break; } } #else int char_len = utf8_char_len(*dot); while (dot < end - 1 && --char_len > 0) dot++; #endif } #endif if (dot < end - 1) dot++; #endif } static int dot_beginning_of_code_line(void) { char *prev_dot; undo_queue_commit(); prev_dot = dot; dot_begin(); // return pointer to first char cur line while (dot < end) { switch (*dot) { case ' ': case '\t': break; default: if (dot >= prev_dot) goto break_out; return 1; } dot++; } break_out: dot = prev_dot; return 0; } static void dot_begin(void) { undo_queue_commit(); dot = begin_line(dot); // return pointer to first char cur line } static char *move_to_col(char *p, int l) { int co; p = begin_line(p); co = 0; while (co < l && p < end) { if (*p == '\n') //vda || *p == '\0') break; if (*p == '\t') { co = next_tabstop(co); } else if (*p < ' ' || *p == 127) { co++; // display as ^X, use 2 columns } co++; p++; } return p; } static void dot_next(void) { undo_queue_commit(); dot = next_line(dot); } static void dot_prev(void) { undo_queue_commit(); dot = prev_line(dot); } static void dot_scroll(int cnt, int dir) { char *q; undo_queue_commit(); for (; cnt > 0; cnt--) { if (dir < 0) { // scroll Backwards // ctrl-Y scroll up one line screenbegin = prev_line(screenbegin); } else { // scroll Forwards // ctrl-E scroll down one line screenbegin = next_line(screenbegin); } } // make sure "dot" stays on the screen so we dont scroll off if (dot < screenbegin) dot = screenbegin; q = end_screen(); // find new bottom line if (dot > q) dot = begin_line(q); // is dot is below bottom line? dot_skip_over_ws(); } static void dot_skip_over_ws(void) { // skip WS while (isspace(*dot) && *dot != '\n' && dot < end - 1) dot++; } static char *bound_dot(char *p) // make sure text[0] <= P < "end" { if (p >= end && end > text) { p = end - 1; indicate_error(); } if (p < text) { p = text; indicate_error(); } return p; } //----- Helper Utility Routines -------------------------------- //---------------------------------------------------------------- //----- Char Routines -------------------------------------------- /* Chars that are part of a word- * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz * Chars that are Not part of a word (stoppers) * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~ * Chars that are WhiteSpace * TAB NEWLINE VT FF RETURN SPACE * DO NOT COUNT NEWLINE AS WHITESPACE */ static char *new_screen(int ro, int co) { int li; free(screen); screensize = ro * co + 8; screen = xmalloc(screensize); // initialize the new screen. assume this will be a empty file. screen_erase(); // non-existent text[] lines start with a tilde (~). for (li = 1; li < ro - 1; li++) { screen[(li * co) + 0] = '~'; } return screen; } #if ENABLE_FEATURE_NANO_SEARCH # if ENABLE_FEATURE_NANO_REGEX_SEARCH # else # if ENABLE_FEATURE_NANO_SETOPTS static int mycmp(const char *s1, const char *s2, int len) { if (ignorecase) { return strncasecmp(s1, s2, len); } return strncmp(s1, s2, len); } # else # define mycmp strncmp # endif # endif #endif /* FEATURE_NANO_SEARCH */ #define CTRL_CODE(x) (1+x-'A') static char *char_insert(char *p, char c, int undo) // insert the char c at 'p' { switch (c) { case CTRL_CODE('V'): p += stupid_insert(p, '^'); // use ^ to indicate literal next refresh(FALSE); // show the ^ c = get_one_char(); *p = c; #if ENABLE_FEATURE_NANO_UNDO switch (undo) { case ALLOW_UNDO: undo_push(p, 1, UNDO_INS); break; case ALLOW_UNDO_CHAIN: undo_push(p, 1, UNDO_INS_CHAIN); break; # if ENABLE_FEATURE_NANO_UNDO_QUEUE case ALLOW_UNDO_QUEUED: undo_push(p, 1, UNDO_INS_QUEUED); break; # endif } #else modified_count++; #endif /* ENABLE_FEATURE_NANO_UNDO */ p++; break; #if 0 // Vi specific case 27: // Is this an ESC? cmd_mode = 0; undo_queue_commit(); end_cmd_q(); // stop adding to q last_status_cksum = 0; // force status update if ((p[-1] != '\n') && (dot > text)) { p--; } break; #endif case 8: case 127: backspace: if (p > text) { p--; p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char } break; default: if (c == erase_char) goto backspace; // insert a char into text[] if (c == 13) c = '\n'; // translate \r to \n #if ENABLE_FEATURE_NANO_UNDO # if ENABLE_FEATURE_NANO_UNDO_QUEUE if (c == '\n') undo_queue_commit(); # endif switch (undo) { case ALLOW_UNDO: undo_push(p, 1, UNDO_INS); break; case ALLOW_UNDO_CHAIN: undo_push(p, 1, UNDO_INS_CHAIN); break; # if ENABLE_FEATURE_NANO_UNDO_QUEUE case ALLOW_UNDO_QUEUED: undo_push(p, 1, UNDO_INS_QUEUED); break; # endif } #else modified_count++; #endif /* ENABLE_FEATURE_NANO_UNDO */ p += 1 + stupid_insert(p, c); // insert the char #if 0&&ENABLE_FEATURE_NANO_UTF8 // Causes segfault! if (c & 0x80) { int utf8_len = utf8_char_len(c); while (--utf8_len > 0) { c = get_one_char(); p += 1 + stupid_insert(p, c); // this may break undo? FIXME } c = 0; } #endif #if ENABLE_FEATURE_NANO_SETOPTS if (showmatch && strchr(")]}", c) != NULL) { showmatching(p - 1); } if (autoindent && c == '\n') { // auto indent the new line char *q; size_t len; q = prev_line(p); // use prev line as template len = strspn(q, " \t"); // space or tab if (len) { uintptr_t bias; bias = text_hole_make(p, len); p += bias; q += bias; #if ENABLE_FEATURE_NANO_UNDO undo_push(p, len, UNDO_INS); #endif memcpy(p, q, len); p += len; } } #endif } return p; } // might reallocate text[]! use p += stupid_insert(p, ...), // and be careful to not use pointers into potentially freed text[]! static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p' { uintptr_t bias; bias = text_hole_make(p, 1); p += bias; *p = c; return bias; } // find matching char of pair () [] {} // will crash if c is not one of these static char *find_pair(char *p, const char c) { const char *braces = "()[]{}"; char match; int dir, level; dir = strchr(braces, c) - braces; dir ^= 1; match = braces[dir]; dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */ // look for match, count levels of pairs (( )) level = 1; for (;;) { p += dir; if (p < text || p >= end) return NULL; if (*p == c) level++; // increase pair levels if (*p == match) { level--; // reduce pair level if (level == 0) return p; // found matching pair } } } #if ENABLE_FEATURE_NANO_SETOPTS // show the matching char of a pair, () [] {} static void showmatching(char *p) { char *q, *save_dot; // we found half of a pair q = find_pair(p, *p); // get loc of matching char if (q == NULL) { indicate_error(); // no matching char } else { // "q" now points to matching pair save_dot = dot; // remember where we are dot = q; // go to new loc refresh(FALSE); // let the user see it mysleep(40); // give user some time dot = save_dot; // go back to old loc refresh(FALSE); } } #endif /* FEATURE_NANO_SETOPTS */ #if ENABLE_FEATURE_NANO_UNDO static void flush_undo_data(void) { struct undo_object *undo_entry; while (undo_stack_tail) { undo_entry = undo_stack_tail; undo_stack_tail = undo_entry->prev; free(undo_entry); } } // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com) static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack { struct undo_object *undo_entry; // "u_type" values // UNDO_INS: insertion, undo will remove from buffer // UNDO_DEL: deleted text, undo will restore to buffer // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete // The CHAIN operations are for handling multiple operations that the user // performs with a single action, i.e. REPLACE mode or find-and-replace commands // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue // for the INS/DEL operation. The raw values should be equal to the values of // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG #if ENABLE_FEATURE_NANO_UNDO_QUEUE // This undo queuing functionality groups multiple character typing or backspaces // into a single large undo object. This greatly reduces calls to malloc() for // single-character operations while typing and has the side benefit of letting // an undo operation remove chunks of text rather than a single character. switch (u_type) { case UNDO_EMPTY: // Just in case this ever happens... return; case UNDO_DEL_QUEUED: if (length != 1) return; // Only queue single characters switch (undo_queue_state) { case UNDO_EMPTY: undo_queue_state = UNDO_DEL; case UNDO_DEL: undo_queue_spos = src; undo_q++; undo_queue[CONFIG_FEATURE_NANO_UNDO_QUEUE_MAX - undo_q] = *src; // If queue is full, dump it into an object if (undo_q == CONFIG_FEATURE_NANO_UNDO_QUEUE_MAX) undo_queue_commit(); return; case UNDO_INS: // Switch from storing inserted text to deleted text undo_queue_commit(); undo_push(src, length, UNDO_DEL_QUEUED); return; } break; case UNDO_INS_QUEUED: if (length != 1) return; switch (undo_queue_state) { case UNDO_EMPTY: undo_queue_state = UNDO_INS; undo_queue_spos = src; case UNDO_INS: undo_q++; // Don't need to save any data for insertions if (undo_q == CONFIG_FEATURE_NANO_UNDO_QUEUE_MAX) undo_queue_commit(); return; case UNDO_DEL: // Switch from storing deleted text to inserted text undo_queue_commit(); undo_push(src, length, UNDO_INS_QUEUED); return; } break; } #else // If undo queuing is disabled, ignore the queuing flag entirely u_type = u_type & ~UNDO_QUEUED_FLAG; #endif // Allocate a new undo object if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) { // For UNDO_DEL objects, save deleted text if ((src + length) == end) length--; // If this deletion empties text[], strip the newline. When the buffer becomes // zero-length, a newline is added back, which requires this to compensate. undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length); memcpy(undo_entry->undo_text, src, length); } else { undo_entry = xzalloc(sizeof(*undo_entry)); } undo_entry->length = length; #if ENABLE_FEATURE_NANO_UNDO_QUEUE if ((u_type & UNDO_USE_SPOS) != 0) { undo_entry->start = undo_queue_spos - text; // use start position from queue } else { undo_entry->start = src - text; // use offset from start of text buffer } u_type = (u_type & ~UNDO_USE_SPOS); #else undo_entry->start = src - text; #endif undo_entry->u_type = u_type; // Push it on undo stack undo_entry->prev = undo_stack_tail; undo_stack_tail = undo_entry; modified_count++; } static void undo_pop(void) // Undo the last operation { int repeat; char *u_start, *u_end; struct undo_object *undo_entry; // Commit pending undo queue before popping (should be unnecessary) undo_queue_commit(); undo_entry = undo_stack_tail; // Check for an empty undo stack if (!undo_entry) { status_line("Already at oldest change"); return; } switch (undo_entry->u_type) { case UNDO_DEL: case UNDO_DEL_CHAIN: // make hole and put in text that was deleted; deallocate text u_start = text + undo_entry->start; text_hole_make(u_start, undo_entry->length); memcpy(u_start, undo_entry->undo_text, undo_entry->length); status_line("Undo [%d] %s %d chars at position %d", modified_count, "restored", undo_entry->length, undo_entry->start ); break; case UNDO_INS: case UNDO_INS_CHAIN: // delete what was inserted u_start = undo_entry->start + text; u_end = u_start - 1 + undo_entry->length; text_hole_delete(u_start, u_end, NO_UNDO); status_line("Undo [%d] %s %d chars at position %d", modified_count, "deleted", undo_entry->length, undo_entry->start ); break; } repeat = 0; switch (undo_entry->u_type) { // If this is the end of a chain, lower modification count and refresh display case UNDO_DEL: case UNDO_INS: dot = (text + undo_entry->start); refresh(FALSE); break; case UNDO_DEL_CHAIN: case UNDO_INS_CHAIN: repeat = 1; break; } // Deallocate the undo object we just processed undo_stack_tail = undo_entry->prev; free(undo_entry); modified_count--; // For chained operations, continue popping all the way down the chain. if (repeat) { undo_pop(); // Follow the undo chain if one exists } } #if ENABLE_FEATURE_NANO_UNDO_QUEUE static void undo_queue_commit(void) // Flush any queued objects to the undo stack { // Pushes the queue object onto the undo stack if (undo_q > 0) { // Deleted character undo events grow from the end undo_push(undo_queue + CONFIG_FEATURE_NANO_UNDO_QUEUE_MAX - undo_q, undo_q, (undo_queue_state | UNDO_USE_SPOS) ); undo_queue_state = UNDO_EMPTY; undo_q = 0; } } #endif #endif /* ENABLE_FEATURE_NANO_UNDO */ // open a hole in text[] // might reallocate text[]! use p += text_hole_make(p, ...), // and be careful to not use pointers into potentially freed text[]! static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole { uintptr_t bias = 0; if (size <= 0) return bias; end += size; // adjust the new END if (end >= (text + text_size)) { char *new_text; text_size += end - (text + text_size) + 10240; new_text = xrealloc(text, text_size); bias = (new_text - text); screenbegin += bias; dot += bias; end += bias; p += bias; #if ENABLE_FEATURE_NANO_YANKMARK { int i; for (i = 0; i < ARRAY_SIZE(mark); i++) if (mark[i]) mark[i] += bias; } #endif text = new_text; } memmove(p + size, p, end - size - p); memset(p, ' ', size); // clear new hole return bias; } // close a hole in text[] // "undo" value indicates if this operation should be undo-able static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive { char *src, *dest; int cnt, hole_size; // move forwards, from beginning // assume p <= q src = q + 1; dest = p; if (q < p) { // they are backward- swap them src = p + 1; dest = q; } hole_size = q - p + 1; cnt = end - src; #if ENABLE_FEATURE_NANO_UNDO switch (undo) { case NO_UNDO: break; case ALLOW_UNDO: undo_push(p, hole_size, UNDO_DEL); break; case ALLOW_UNDO_CHAIN: undo_push(p, hole_size, UNDO_DEL_CHAIN); break; # if ENABLE_FEATURE_NANO_UNDO_QUEUE case ALLOW_UNDO_QUEUED: undo_push(p, hole_size, UNDO_DEL_QUEUED); break; # endif } modified_count--; #endif if (src < text || src > end) goto thd0; if (dest < text || dest >= end) goto thd0; modified_count++; if (src >= end) goto thd_atend; // just delete the end of the buffer memmove(dest, src, cnt); thd_atend: end = end - hole_size; // adjust the new END if (dest >= end) dest = end - 1; // make sure dest in below end-1 if (end <= text) dest = end = text; // keep pointers valid thd0: return dest; } // copy text into register, then delete text. // if dist <= 0, do not include, or go past, a NewLine // static char *yank_delete(char *start, char *stop, int dist, int undo) { char *p; // make sure start <= stop if (start > stop) { // they are backwards, reverse them p = start; start = stop; stop = p; } if (dist <= 0) { // we cannot cross NL boundaries p = start; if (*p == '\n') return p; // dont go past a NewLine for (; p + 1 <= stop; p++) { if (p[1] == '\n') { stop = p; // "stop" just before NewLine break; } } } p = start; #if ENABLE_FEATURE_NANO_YANKMARK text_yank(start, stop); #endif p = text_hole_delete(start, stop, undo); return p; } static void show_help(void) { puts("These features are available:" #if ENABLE_FEATURE_NANO_SEARCH "\n\tPattern searches with / and ?" #endif #if ENABLE_FEATURE_NANO_YANKMARK "\n\tLine marking with 'x" "\n\tNamed buffers with \"x" #endif #if ENABLE_FEATURE_NANO_READONLY //not implemented: "\n\tReadonly if nano is called as \"view\"" //redundant: usage text says this too: "\n\tReadonly with -R command line arg" #endif #if ENABLE_FEATURE_NANO_SET "\n\tSome colon mode commands with :" #endif #if ENABLE_FEATURE_NANO_SETOPTS "\n\tSettable options with \":set\"" #endif #if ENABLE_FEATURE_NANO_USE_SIGNALS "\n\tSignal catching- ^C" "\n\tJob suspend and resume with ^Z" #endif #if ENABLE_FEATURE_NANO_WIN_RESIZE "\n\tAdapt to window re-sizes" #endif ); } #if ENABLE_FEATURE_NANO_YANKMARK \ || ENABLE_FEATURE_NANO_SEARCH \ || ENABLE_FEATURE_NANO_CRASHME // might reallocate text[]! use p += string_insert(p, ...), // and be careful to not use pointers into potentially freed text[]! static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p' { uintptr_t bias; int i; i = strlen(s); #if ENABLE_FEATURE_NANO_UNDO switch (undo) { case ALLOW_UNDO: undo_push(p, i, UNDO_INS); break; case ALLOW_UNDO_CHAIN: undo_push(p, i, UNDO_INS_CHAIN); break; } #endif bias = text_hole_make(p, i); p += bias; memcpy(p, s, i); #if ENABLE_FEATURE_NANO_YANKMARK { int cnt; for (cnt = 0; *s != '\0'; s++) { if (*s == '\n') cnt++; } if (cnt > 4) status_line("Put %d lines (%d chars)", cnt, i); } #endif return bias; } #endif #if ENABLE_FEATURE_NANO_YANKMARK static char *text_yank(char *p, char *q) // copy text into a register { int cnt = q - p; if (cnt < 0) { // they are backwards- reverse them p = q; cnt = -cnt; } if (yank_append) { int l = strlen(clipboard); clipboard = xrealloc(clipboard, l + cnt + 2); memcpy(clipboard + l, p, cnt + 1); clipboard[l + cnt + 1] = 0; } else { free(clipboard); // if already a yank register, free it clipboard = xstrndup(p, cnt + 1); } return p; } #endif /* FEATURE_NANO_YANKMARK */ //----- Set terminal attributes -------------------------------- static void rawmode(void) { // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals set_termios_to_raw(NANO_STDIN_FD, &term_orig, TERMIOS_RAW_CRNL); erase_char = term_orig.c_cc[VERASE]; } static void cookmode(void) { fflush(NULL); tcsetattr_stdin_TCSANOW(&term_orig); } #if ENABLE_FEATURE_NANO_USE_SIGNALS //----- Come here when we get a window resize signal --------- static void winch_sig(int sig UNUSED_PARAM) { int save_errno = errno; // FIXME: do it in main loop!!! signal(SIGWINCH, winch_sig); query_screen_dimensions(); new_screen(rows, columns); // get memory for virtual screen redraw(TRUE); // re-draw the screen errno = save_errno; } //----- Come here when we get a continue signal ------------------- static void cont_sig(int sig UNUSED_PARAM) { int save_errno = errno; rawmode(); // terminal to "raw" last_status_cksum = 0; // force status update redraw(TRUE); // re-draw the screen signal(SIGTSTP, suspend_sig); signal(SIGCONT, SIG_DFL); //kill(my_pid, SIGCONT); // huh? why? we are already "continued"... errno = save_errno; } //----- Come here when we get a Suspend signal ------------------- static void suspend_sig(int sig UNUSED_PARAM) { int save_errno = errno; go_bottom_and_clear_to_eol(); cookmode(); // terminal to "cooked" signal(SIGCONT, cont_sig); signal(SIGTSTP, SIG_DFL); kill(my_pid, SIGTSTP); errno = save_errno; } //----- Come here when we get a signal --------------------------- static void catch_sig(int sig) { signal(SIGINT, catch_sig); siglongjmp(restart, sig); } #endif /* FEATURE_NANO_USE_SIGNALS */ static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready { struct pollfd pfd[1]; if (hund != 0) fflush(NULL); pfd[0].fd = NANO_STDIN_FD; pfd[0].events = POLLIN; return safe_poll(pfd, 1, hund*10) > 0; } static int get_one_char(void) // read (maybe cursor) key from stdin { int c; fflush(NULL); // Wait for input. TIMEOUT = -1 makes read_key wait even // on nonblocking stdin. // Note: read_key sets errno to 0 on success. again: c = read_key(NANO_STDIN_FD, readbuffer, /*timeout:*/ -1); if (c == -1) { // EOF/error if (errno == EAGAIN) // paranoia goto again; go_bottom_and_clear_to_eol(); cookmode(); // terminal to "cooked" bb_error_msg_and_die("can't read user input"); } return c; } // Get input line (uses "status line" area) static char *get_input_line(const char *prompt, const char *value) { // char [MAX_INPUT_LEN] #define buf get_input_line__buf int c; int i; int selected = 0; int prompt_len = strlen(prompt); strcpy(buf, prompt); strncat(buf, value, sizeof(buf)); last_status_cksum = 0; // force status update go_bottom_and_clear_to_eol(); write1(buf); // write out the :, /, or ? prompt if (value != NULL && value[0]) { place_cursor(rows - 1, prompt_len); selected = 1; } i = strlen(buf); while (i < MAX_INPUT_LEN) { c = get_one_char(); if (selected) { switch (c) { case 0: case 27: case '\n': case '\r': break; case KEYCODE_RIGHT: case KEYCODE_END: place_cursor(rows - 1, i = strlen(buf)); selected = 0; c = 0; break; default: strcpy(buf, prompt); i = prompt_len; clear_to_eol(); selected = 0; } } switch (c) { case 27: get_one_char(); // fallthrough case 0: break; case '\n': case '\r': goto end_of_input; case 8: case 127: backspace_2: if (i > prompt_len) { // erase prev char buf[--i] = '\0'; write1("\b \b"); } break; default: if (c == erase_char) goto backspace_2; #if 0 // ENABLE_FEATURE_NANO_UTF8 this shouldn't actually be needed as handling is equivalent here, this may change if more complex line editing is required char_len = utf8_char_len(c); do { #endif buf[i] = c; buf[++i] = '\0'; write_char(c); #if 0 // ENABLE_FEATURE_NANO_UTF8 ditto } while (--char_len > 0); #endif } } end_of_input: refresh(FALSE); return buf + prompt_len; #undef buf } // might reallocate text[]! static int file_insert(const char *fn, char *p, int initial) { int cnt = -1; int fd, size; struct stat statbuf; if (p < text) p = text; if (p > end) p = end; #if ENABLE_FEATURE_NANO_STDIO if (nano_stdio_mode) { fd = STDIN_FILENO; } else { #endif fd = open(fn, O_RDONLY); #if ENABLE_FEATURE_NANO_STDIO } #endif if (fd < 0) { if (!initial) status_line_bold_errno(fn); return cnt; } #if ENABLE_FEATURE_NANO_STDIO if (nano_stdio_mode) { size = 0x100; while (TRUE) { p += text_hole_make(p, size); cnt = full_read(fd, p, size); if (cnt != size) break; p += cnt; } } else { #endif /* Validate file */ if (fstat(fd, &statbuf) < 0) { status_line_bold_errno(fn); goto fi; } if (!S_ISREG(statbuf.st_mode)) { status_line_bold("'%s' is not a regular file", fn); goto fi; } size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX); p += text_hole_make(p, size); cnt = full_read(fd, p, size); #if ENABLE_FEATURE_NANO_STDIO } #endif if (cnt < 0) { status_line_bold_errno(fn); p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert } else if (cnt < size) { // There was a partial read, shrink unused space p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO); #if ENABLE_FEATURE_NANO_STDIO if (!nano_stdio_mode) { #endif status_line_bold("can't read '%s'", fn); #if ENABLE_FEATURE_NANO_STDIO } #endif } fi: #if ENABLE_FEATURE_NANO_STDIO if (!nano_stdio_mode) { #endif close(fd); #if ENABLE_FEATURE_NANO_STDIO } #endif #if ENABLE_FEATURE_NANO_STDIO if (!nano_stdio_mode) { #endif #if ENABLE_FEATURE_NANO_READONLY if (initial && ((access(fn, W_OK) < 0) || /* root will always have access() * so we check fileperms too */ !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) ) ) { SET_READONLY_FILE(readonly_mode); } #endif #if ENABLE_FEATURE_NANO_STDIO } #endif return cnt; } static int file_write(char *fn, char *first, char *last) { int fd, cnt, charcnt; #if ENABLE_FEATURE_NANO_STDIO if (nano_stdio_mode) { fd = STDOUT_FILENO; } else { #endif if (fn == 0) { status_line_bold("No current filename"); return -2; } /* By popular request we do not open file with O_TRUNC, * but instead ftruncate() it _after_ successful write. * Might reduce amount of data lost on power fail etc. */ fd = open(fn, (O_WRONLY | O_CREAT), 0666); #if ENABLE_FEATURE_NANO_STDIO } #endif if (fd < 0) return -1; cnt = last - first + 1; charcnt = full_write(fd, first, cnt); #if ENABLE_FEATURE_NANO_STDIO if (!nano_stdio_mode) { #endif ftruncate(fd, charcnt); #if ENABLE_FEATURE_NANO_STDIO } #endif if (charcnt == cnt) { // good write //modified_count = FALSE; } else { charcnt = 0; } #if ENABLE_FEATURE_NANO_STDIO if (!nano_stdio_mode) { #endif close(fd); #if ENABLE_FEATURE_NANO_STDIO } #endif return charcnt; } //----- Terminal Drawing --------------------------------------- // The terminal is made up of 'rows' line of 'columns' columns. // classically this would be 24 x 80. // screen coordinates // 0,0 ... 0,79 // 1,0 ... 1,79 // . ... . // . ... . // 22,0 ... 22,79 // 23,0 ... 23,79 <- status line //----- Move the cursor to row x col (count from 0, not 1) ------- static void place_cursor(int row, int col) { char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2]; if (row < 0) row = 0; if (row >= rows) row = rows - 1; if (col < 0) col = 0; if (col >= columns) col = columns - 1; sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1); write1(cm1); } //----- Erase from cursor to end of line ----------------------- static void clear_to_eol(void) { write1(ESC_CLEAR2EOL); } static void go_bottom_and_clear_to_eol(void) { place_cursor(rows - 1, 0); clear_to_eol(); } //----- Erase from cursor to end of screen ----------------------- static void clear_to_eos(void) { write1(ESC_CLEAR2EOS); } //----- Start standout mode ------------------------------------ static void standout_start(void) { write1(ESC_BOLD_TEXT); } //----- End standout mode -------------------------------------- static void standout_end(void) { write1(ESC_NORM_TEXT); } static void indicate_error(void) { #if ENABLE_FEATURE_NANO_CRASHME if (crashme > 0) return; // generate a random command #endif write1(ESC_BELL); } //----- Screen[] Routines -------------------------------------- //----- Erase the Screen[] memory ------------------------------ static void screen_erase(void) { memset(screen, ' ', screensize); // clear new screen } static int bufsum(char *buf, int count) { int sum = 0; char *e = buf + count; while (buf < e) sum += (unsigned char) *buf++; return sum; } //----- Draw the status line at bottom of the screen ------------- static void show_status_line(void) { int cnt = 0, cksum = 0; // either we already have an error or status message, or we // create one. if (!have_status_msg) { cnt = format_edit_status(); cksum = bufsum(status_buffer, cnt); } if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) { last_status_cksum = cksum; // remember if we have seen this line go_bottom_and_clear_to_eol(); write1(status_buffer); if (have_status_msg) { if (((int)strlen(status_buffer) - (have_status_msg - 1)) > (columns - 1) ) { have_status_msg = 0; Hit_Return(); } have_status_msg = 0; } place_cursor(crow, ccol); // put cursor back in correct place } fflush(NULL); } //----- format the status buffer, the bottom line of screen ------ // format status buffer, with STANDOUT mode static void status_line_bold(const char *format, ...) { va_list args; va_start(args, format); strcpy(status_buffer, ESC_BOLD_TEXT); vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args); strcat(status_buffer, ESC_NORM_TEXT); va_end(args); have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2; } static void status_line_bold_errno(const char *fn) { status_line_bold("'%s' %s", fn, strerror(errno)); } // format status buffer static void status_line(const char *format, ...) { va_list args; va_start(args, format); vsprintf(status_buffer, format, args); va_end(args); have_status_msg = 1; } // copy s to buf, convert unprintable static void print_literal(char *buf, const char *s) { char *d; unsigned char c; buf[0] = '\0'; if (!s[0]) s = "(NULL)"; d = buf; for (; *s; s++) { int c_is_no_print; c = *s; c_is_no_print = (c & 0x80) && !Isprint(c); if (c_is_no_print) { strcpy(d, ESC_NORM_TEXT); d += sizeof(ESC_NORM_TEXT)-1; c = '.'; } if (c < ' ' || c == 0x7f) { *d++ = '^'; c |= '@'; /* 0x40 */ if (c == 0x7f) c = '?'; } *d++ = c; *d = '\0'; if (c_is_no_print) { strcpy(d, ESC_BOLD_TEXT); d += sizeof(ESC_BOLD_TEXT)-1; } if (*s == '\n') { *d++ = '$'; *d = '\0'; } if (d - buf > MAX_INPUT_LEN - 10) // paranoia break; } } static void not_implemented(const char *s) { char buf[MAX_INPUT_LEN]; print_literal(buf, s); status_line_bold("\'%s\' is not implemented", buf); } #define NANO_DEBUG 0 #if NANO_DEBUG static char last_key_code = 0; #endif // show file status on status line static int format_edit_status(void) { #define tot format_edit_status__tot int cur, percent, ret, trunc_at; // modified_count is now a counter rather than a flag. this // helps reduce the amount of line counting we need to do. // (this will cause a mis-reporting of modified status // once every MAXINT editing operations.) // it would be nice to do a similar optimization here -- if // we haven't done a motion that could have changed which line // we're on, then we shouldn't have to do this count_lines() cur = count_lines(text, dot); // count_lines() is expensive. // Call it only if something was changed since last time // we were here: if (modified_count != last_modified_count) { tot = cur + count_lines(dot, end - 1) - 1; last_modified_count = modified_count; } // current line percent // ------------- ~~ ---------- // total lines 100 if (tot > 0) { percent = (100 * cur) / tot; } else { cur = tot = 0; percent = 100; } trunc_at = columns < STATUS_BUFFER_LEN-1 ? columns : STATUS_BUFFER_LEN-1; ret = snprintf(status_buffer, trunc_at+1, #if ENABLE_FEATURE_NANO_READONLY " %s%s%s %d/%d %d%%" #else " %s%s %d/%d %d%%" #endif #if 0 " %02x %02x %02x %02x %02x" #endif #if NANO_DEBUG " %08x" #endif #if 0 " %ld" #endif " ^X=Exit ^O=Save ^W=Search ^K=Cut ^U=Paste M-u=Undo", (current_filename != NULL ? current_filename : "No file"), #if ENABLE_FEATURE_NANO_READONLY (readonly_mode ? " [Readonly]" : ""), #endif (modified_count ? " [Modified]" : ""), cur, tot, percent #if 0 (dot < end) ? dot[0] : 0, (dot + 1 < end) ? dot[1] : 0, (dot + 2 < end) ? dot[2] : 0, (dot + 3 < end) ? dot[3] : 0, (dot + 4 < end) ? dot[4] : 0, (dot + 5 < end) ? dot[5] : 0, #endif #if NANO_DEBUG last_key_code, #endif #if 0 dot - text #endif ); if (ret >= 0 && ret < trunc_at) return ret; /* it all fit */ return trunc_at; /* had to truncate */ #undef tot } //----- Force refresh of all Lines ----------------------------- static void redraw(int full_screen) { place_cursor(0, 0); clear_to_eos(); screen_erase(); // erase the internal screen buffer last_status_cksum = 0; // force status update refresh(full_screen); // this will redraw the entire display show_status_line(); } //----- Format a text[] line into a buffer --------------------- static char* format_line(char *src /*, int li*/) { unsigned char c; int co; int ofs = offset; char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2] c = '~'; // char in col 0 in non-existent lines is '~' co = 0; while (co < columns + tabstop) { // have we gone past the end? if (src < end) { c = *src++; if (c == '\n') break; #if 0 && ENABLE_FEATURE_NANO_UTF8 // breaks redraw! if (c & 0x80) { do { dest[co++] = c; if (src < end) { c = *src++; } } while (c & 0x80); goto continue_printing; } if (!Isprint(c)) { c = '.'; } #else if ((c & 0x80) && !Isprint(c)) { c = '.'; } #endif if (c < ' ' || c == 0x7f) { if (c == '\t') { c = ' '; // co % 8 != 7 while ((co % tabstop) != (tabstop - 1)) { dest[co++] = c; } } else { dest[co++] = '^'; if (c == 0x7f) c = '?'; else c += '@'; // Ctrl-X -> 'X' } } } continue_printing: dest[co++] = c; // discard scrolled-off-to-the-left portion, // in tabstop-sized pieces if (ofs >= tabstop && co >= tabstop) { memmove(dest, dest + tabstop, co); co -= tabstop; ofs -= tabstop; } if (src >= end) break; } // check "short line, gigantic offset" case if (co < ofs) ofs = co; // discard last scrolled off part co -= ofs; dest += ofs; // fill the rest with spaces if (co < columns) memset(&dest[co], ' ', columns - co); return dest; } //----- Refresh the changed screen lines ----------------------- // Copy the source line from text[] into the buffer and note // if the current screenline is different from the new buffer. // If they differ then that line needs redrawing on the terminal. // static void refresh(int full_screen) { #define old_offset refresh__old_offset int li, changed; char *tp, *sp; // pointer into text[] and screen[] if (ENABLE_FEATURE_NANO_WIN_RESIZE IF_FEATURE_NANO_ASK_TERMINAL(&& !G.get_rowcol_error) ) { unsigned c = columns, r = rows; query_screen_dimensions(); full_screen |= (c - columns) | (r - rows); } sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot") tp = screenbegin; // index into text[] of top line // compare text[] to screen[] and mark screen[] lines that need updating for (li = 0; li < rows - 1; li++) { int cs, ce; // column start & end char *out_buf; // format current text line out_buf = format_line(tp /*, li*/); // skip to the end of the current text[] line if (tp < end) { char *t = memchr(tp, '\n', end - tp); if (!t) t = end - 1; tp = t + 1; } // see if there are any changes between virtual screen and out_buf changed = FALSE; // assume no change cs = 0; ce = columns - 1; sp = &screen[li * columns]; // start of screen line if (full_screen) { // force re-draw of every single column from 0 - columns-1 goto re0; } // compare newly formatted buffer with virtual screen // look forward for first difference between buf and screen for (; cs <= ce; cs++) { if (out_buf[cs] != sp[cs]) { changed = TRUE; // mark for redraw break; } } // look backward for last difference between out_buf and screen for (; ce >= cs; ce--) { if (out_buf[ce] != sp[ce]) { changed = TRUE; // mark for redraw break; } } // now, cs is index of first diff, and ce is index of last diff // if horz offset has changed, force a redraw if (offset != old_offset) { re0: changed = TRUE; } // make a sanity check of columns indexes if (cs < 0) cs = 0; if (ce > columns - 1) ce = columns - 1; if (cs > ce) { cs = 0; ce = columns - 1; } // is there a change between virtual screen and out_buf if (changed) { // copy changed part of buffer to virtual screen memcpy(sp+cs, out_buf+cs, ce-cs+1); place_cursor(li, cs); // write line out to terminal fwrite(&sp[cs], ce - cs + 1, 1, NANO_STDOUT); } } place_cursor(crow, ccol); old_offset = offset; #undef old_offset } static int save_file(void) { int cnt; if (ENABLE_FEATURE_NANO_READONLY && readonly_mode) { status_line_bold("'%s' is read only", current_filename); return 0; } cnt = file_write(current_filename, text, end - 1); if (cnt < 0) { if (cnt == -1) status_line_bold("Write error: %s", strerror(errno)); return 0; } if (cnt != (end - 1 - text + 1)) { status_line_bold("File truncated: %s", strerror(errno)); return 0; } modified_count = 0; return 1; } static int get_filename(void) { char *p = get_input_line("Save to file: ", current_filename); if (p[0] == 0) return 0; free(current_filename); current_filename = xstrdup(p); return 1; } #if ENABLE_FEATURE_NANO_INDENT_SENSITIVE static void newline_cmd(void) { char *save_dot = dot; char *a, *b, *s; if (dot_beginning_of_code_line()) { b = dot; dot_begin(); a = dot; dot = save_dot; s = xstrndup(a, (b - a)); s[b - a]; dot = char_insert(dot, '\n', ALLOW_UNDO_QUEUED); dot += string_insert(dot, s, ALLOW_UNDO) + b - a; free(s); } else { dot = char_insert(dot, '\n', ALLOW_UNDO_QUEUED); } } #endif #if ENABLE_FEATURE_NANO_FOLDING static void fold_cmd(void) { } #endif //--------------------------------------------------------------------- //----- the Ascii Chart ----------------------------------------------- // // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 ' // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f / // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ? // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _ // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del //--------------------------------------------------------------------- //----- Execute a Nano Command ----------------------------------- static void do_cmd(int c) { char *p, *q, *save_dot; char buf[12]; // c1 = c; // quiet the compiler // cnt = yf = 0; // quiet the compiler // p = q = save_dot = buf; // quiet the compiler memset(buf, '\0', sizeof(buf)); #if NANO_DEBUG last_key_code = c; #endif // show_status_line(); // isn't this called twice per character? /* if this is a cursor key, skip these checks */ switch (c) { #if 0 // ENABLE_FEATURE_NANO_INSERT // Unsupported yet, should insert file at point case KEYCODE_INSERT: #endif default: if (1 <= c || Isprint(c)) { dot = char_insert(dot, c, ALLOW_UNDO_QUEUED); break; } buf[0] = c; buf[1] = '\0'; not_implemented(buf); end_cmd_q(); break; //case 0x01: // soh //case 0x09: // ht //case 0x0b: // vt //case 0x0e: // so //case 0x0f: // si //case 0x10: // dle //case 0x11: // dc1 //case 0x13: // dc3 #if ENABLE_FEATURE_NANO_CRASHME case 0x14: // dc4 ctrl-T crashme = (crashme == 0) ? 1 : 0; break; #endif //case 0x16: // syn //case 0x17: // etb //case 0x18: // can //case 0x1c: // fs //case 0x1d: // gs //case 0x1e: // rs //case 0x1f: // us //case '!': // !- //case '#': // #- //case '&': // &- //case '(': // (- //case ')': // )- //case '*': // *- //case '=': // =- //case '@': // @- //case 'F': // F- //case 'K': // K- //case 'Q': // Q- //case 'S': // S- //case 'T': // T- //case 'V': // V- //case '[': // [- //case '\\': // \- //case ']': // ]- //case '_': // _- //case '`': // `- //case 'v': // v- case 0x00: // nul- ignore break; case 2: // ctrl-B scroll up full screen case KEYCODE_PAGEUP: // Cursor Key Page Up dot_scroll(rows - 2, -1); break; case 4: // ctrl-D scroll down half screen dot_scroll((rows - 2) / 2, 1); break; case 5: // ctrl-E scroll down one line dot_scroll(1, 1); break; case 6: // ctrl-F scroll down full screen case KEYCODE_PAGEDOWN: // Cursor Key Page Down dot_scroll(rows - 2, 1); break; case 7: // ctrl-G show current status last_status_cksum = 0; // force status update break; case KEYCODE_LEFT: // cursor key Left dot_left(); break; case 8: case 0x7f: undo_queue_commit(); save_dot = dot; dot_left(); if (dot != save_dot) { dot = text_hole_delete(dot, save_dot-1, ALLOW_UNDO); } break; case 10: // Newline ^J case KEYCODE_DOWN: // cursor key Down dot_next(); // go to next B-o-l // try stay in same col dot = move_to_col(dot, ccol + offset); break; #if ENABLE_FEATURE_NANO_INDENT_SENSITIVE case '\r': newline_cmd(); break; #endif case 12: // ctrl-L force redraw whole screen case 18: // ctrl-R force redraw place_cursor(0, 0); clear_to_eos(); //mysleep(10); // why??? screen_erase(); // erase the internal screen buffer last_status_cksum = 0; // force status update refresh(TRUE); // this will redraw the entire display break; case CTRL_CODE('K'): #if ENABLE_FEATURE_NANO_YANKMARK if (yank_append && (dot - text) != previous_yank_position) { yank_append = 0; } #endif save_dot = begin_line(dot); dot = end_line(dot); dot = yank_delete(save_dot, dot, 1, ALLOW_UNDO); #if ENABLE_FEATURE_NANO_YANKMARK yank_append = 1; previous_yank_position = dot - text; #endif break; case CTRL_CODE('O'): #if ENABLE_FEATURE_NANO_STDIO if (nano_stdio_mode) break; #endif undo_queue_commit(); if (get_filename()) save_file(); break; #if ENABLE_FEATURE_NANO_YANKMARK case CTRL_CODE('U'): if (clipboard == NULL) { status_line_bold("Nothing in clipboard"); break; } dot += string_insert(dot, clipboard, ALLOW_UNDO); // insert the string yank_append = 0; break; #endif case CTRL_CODE('X'): undo_queue_commit(); #if ENABLE_FEATURE_NANO_STDIO if (nano_stdio_mode) { save_file(); } else #endif if (modified_count != 0) { go_bottom_and_clear_to_eol(); write1("Save? [y/n]"); switch (get_one_char()) { case 'n': case 'N': // modified_count = 0; break; case 'y': case 'Y': if ((current_filename != NULL || get_filename()) && save_file()) break; // fallthrough default: goto continue_editing; } } editing = 0; continue_editing: break; #if ENABLE_FEATURE_NANO_SEARCH case CTRL_CODE('W'): #define buf search__buf p = get_input_line("Search: ", buf); strcpy(buf, p); q = dot + 1 < end ? strstr(dot+1, p) : NULL; if (q != NULL) { dot = q; } else { // This may scan parts of the buffer twice but is still correct and much simpler to implement. q = strstr(text, p); if (q != NULL) { status_line_bold("Search wrapped!", p); dot = q; } else { status_line_bold("String not found: %s", p); } } #undef buf break; #endif case 25: // ctrl-Y scroll up one line dot_scroll(1, -1); break; case KEYCODE_RIGHT: // Cursor Key Right dot_right(); break; case 27: switch (get_one_char()) { #if ENABLE_FEATURE_NANO_UNDO case 'u': // u- undo last operation undo_pop(); break; #endif } break; case KEYCODE_END: // Cursor Key End dot = end_line(dot); break; case KEYCODE_DELETE: undo_queue_commit(); save_dot = dot; dot_right(); if (dot != save_dot) { dot = text_hole_delete(save_dot, dot-1, ALLOW_UNDO); } break; case KEYCODE_UP: // cursor key Up dot_prev(); dot = move_to_col(dot, ccol + offset); // try stay in same col break; case KEYCODE_HOME: // Cursor Key Home #if ENABLE_FEATURE_NANO_INDENT_SENSITIVE save_dot = dot; dot_beginning_of_code_line(); if (save_dot != dot) break; #endif dot_begin(); #if ENABLE_FEATURE_NANO_FOLDING if (save_dot != dot) break; fold_cmd(); #endif break; // The Fn keys could point to do_macro which could translate them #if 0 case KEYCODE_FUN1: // Function Key F1 case KEYCODE_FUN2: // Function Key F2 case KEYCODE_FUN3: // Function Key F3 case KEYCODE_FUN4: // Function Key F4 case KEYCODE_FUN5: // Function Key F5 case KEYCODE_FUN6: // Function Key F6 case KEYCODE_FUN7: // Function Key F7 case KEYCODE_FUN8: // Function Key F8 case KEYCODE_FUN9: // Function Key F9 case KEYCODE_FUN10: // Function Key F10 case KEYCODE_FUN11: // Function Key F11 case KEYCODE_FUN12: // Function Key F12 break; #endif } // if text[] just became empty, add back an empty line // it is OK for dot to exactly equal to end, otherwise check dot validity if (dot != end) { dot = bound_dot(dot); // make sure "dot" is valid } } /* NB! the CRASHME code is unmaintained, and doesn't currently build */ #if ENABLE_FEATURE_NANO_CRASHME static int totalcmds = 0; static int Mp = 85; // Movement command Probability static int Np = 90; // Non-movement command Probability static int Dp = 96; // Delete command Probability static int Ip = 97; // Insert command Probability static int Yp = 98; // Yank command Probability static int Pp = 99; // Put command Probability static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0; static const char chars[20] = "\t012345 abcdABCD-=.$"; static const char *const words[20] = { "this", "is", "a", "test", "broadcast", "the", "emergency", "of", "system", "quick", "brown", "fox", "jumped", "over", "lazy", "dogs", "back", "January", "Febuary", "March" }; static const char *const lines[20] = { "You should have received a copy of the GNU General Public License\n", "char c, cm, *cmd, *cmd1;\n", "generate a command by percentages\n", "Numbers may be typed as a prefix to some commands.\n", "Quit, discarding changes!\n", "Forced write, if permission originally not valid.\n", "In general, any ex or ed command (such as substitute or delete).\n", "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n", "Please get w/ me and I will go over it with you.\n", "The following is a list of scheduled, committed changes.\n", "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n", "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n", "Any question about transactions please contact Sterling Huxley.\n", "I will try to get back to you by Friday, December 31.\n", "This Change will be implemented on Friday.\n", "Let me know if you have problems accessing this;\n", "Sterling Huxley recently added you to the access list.\n", "Would you like to go to lunch?\n", "The last command will be automatically run.\n", "This is too much english for a computer geek.\n", }; static char *multilines[20] = { "You should have received a copy of the GNU General Public License\n", "char c, cm, *cmd, *cmd1;\n", "generate a command by percentages\n", "Numbers may be typed as a prefix to some commands.\n", "Quit, discarding changes!\n", "Forced write, if permission originally not valid.\n", "In general, any ex or ed command (such as substitute or delete).\n", "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n", "Please get w/ me and I will go over it with you.\n", "The following is a list of scheduled, committed changes.\n", "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n", "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n", "Any question about transactions please contact Sterling Huxley.\n", "I will try to get back to you by Friday, December 31.\n", "This Change will be implemented on Friday.\n", "Let me know if you have problems accessing this;\n", "Sterling Huxley recently added you to the access list.\n", "Would you like to go to lunch?\n", "The last command will be automatically run.\n", "This is too much english for a computer geek.\n", }; // create a random command to execute static void crash_dummy() { static int sleeptime; // how long to pause between commands char c, cm, *cmd, *cmd1; int i, cnt, thing, rbi, startrbi, percent; // "dot" movement commands cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL"; // is there already a command running? if (readbuffer[0] > 0) goto cd1; cd0: readbuffer[0] = 'X'; startrbi = rbi = 1; sleeptime = 0; // how long to pause between commands memset(readbuffer, '\0', sizeof(readbuffer)); // generate a command by percentages percent = (int) lrand48() % 100; // get a number from 0-99 if (percent < Mp) { // Movement commands // available commands cmd = cmd1; M++; } else if (percent < Np) { // non-movement commands cmd = "mz<>\'\""; // available commands N++; } else if (percent < Dp) { // Delete commands cmd = "dx"; // available commands D++; } else if (percent < Ip) { // Inset commands cmd = "iIaAsrJ"; // available commands I++; } else if (percent < Yp) { // Yank commands cmd = "yY"; // available commands Y++; } else if (percent < Pp) { // Put commands cmd = "pP"; // available commands P++; } else { // We do not know how to handle this command, try again U++; goto cd0; } // randomly pick one of the available cmds from "cmd[]" i = (int) lrand48() % strlen(cmd); cm = cmd[i]; if (strchr(":\024", cm)) goto cd0; // dont allow colon or ctrl-T commands readbuffer[rbi++] = cm; // put cmd into input buffer // now we have the command- // there are 1, 2, and multi char commands // find out which and generate the rest of command as necessary if (strchr("dmryz<>\'\"", cm)) { // 2-char commands cmd1 = " \n\r0$^-+wWeEbBhjklHL"; if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[] cmd1 = "abcdefghijklmnopqrstuvwxyz"; } thing = (int) lrand48() % strlen(cmd1); // pick a movement command c = cmd1[thing]; readbuffer[rbi++] = c; // add movement to input buffer } if (strchr("iIaAsc", cm)) { // multi-char commands if (cm == 'c') { // change some thing thing = (int) lrand48() % strlen(cmd1); // pick a movement command c = cmd1[thing]; readbuffer[rbi++] = c; // add movement to input buffer } thing = (int) lrand48() % 4; // what thing to insert cnt = (int) lrand48() % 10; // how many to insert for (i = 0; i < cnt; i++) { if (thing == 0) { // insert chars readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))]; } else if (thing == 1) { // insert words strcat(readbuffer, words[(int) lrand48() % 20]); strcat(readbuffer, " "); sleeptime = 0; // how fast to type } else if (thing == 2) { // insert lines strcat(readbuffer, lines[(int) lrand48() % 20]); sleeptime = 0; // how fast to type } else { // insert multi-lines strcat(readbuffer, multilines[(int) lrand48() % 20]); sleeptime = 0; // how fast to type } } strcat(readbuffer, "\033"); } readbuffer[0] = strlen(readbuffer + 1); cd1: totalcmds++; if (sleeptime > 0) mysleep(sleeptime); // sleep 1/100 sec } // test to see if there are any errors static void crash_test() { static time_t oldtim; time_t tim; char d[2], msg[80]; msg[0] = '\0'; if (end < text) { strcat(msg, "end textend) { strcat(msg, "end>textend "); } if (dot < text) { strcat(msg, "dot end) { strcat(msg, "dot>end "); } if (screenbegin < text) { strcat(msg, "screenbegin end - 1) { strcat(msg, "screenbegin>end-1 "); } if (msg[0]) { printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s", totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT); fflush_all(); while (safe_read(NANO_STDIN_FD, d, 1) > 0) { if (d[0] == '\n' || d[0] == '\r') break; } } tim = time(NULL); if (tim >= (oldtim + 3)) { sprintf(status_buffer, "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d", totalcmds, M, N, I, D, Y, P, U, end - text + 1); oldtim = tim; } } #endif int FAST_FUNC get_terminal_width_height(int fd, unsigned *width, unsigned *height) { struct winsize win; int err; #if ENABLE_FEATURE_NANO_STDIO if (fd == -1) { fd = nano_stdin_fd; } #else int close_me = -1; if (fd == -1) { if (isatty(STDOUT_FILENO)) fd = STDOUT_FILENO; else if (isatty(STDERR_FILENO)) fd = STDERR_FILENO; else if (isatty(STDIN_FILENO)) fd = STDIN_FILENO; else close_me = fd = open("/dev/tty", O_RDONLY); } #endif win.ws_row = 0; win.ws_col = 0; /* I've seen ioctl returning 0, but row/col is (still?) 0. * We treat that as an error too. */ err = ioctl(fd, TIOCGWINSZ, &win) != 0 || win.ws_row == 0; if (height) *height = wh_helper(win.ws_row, 24, "LINES", &err); if (width) *width = wh_helper(win.ws_col, 80, "COLUMNS", &err); #if !ENABLE_FEATURE_NANO_STDIO if (close_me >= 0) close(close_me); #endif return err; } int FAST_FUNC set_termios_to_raw(int fd, struct termios *oldterm, int flags) { //TODO: lineedit, microcom and less might be adapted to use this too: // grep for "tcsetattr" struct termios newterm; tcgetattr(fd, oldterm); newterm = *oldterm; /* Turn off buffered input (ICANON) * Turn off echoing (ECHO) * and separate echoing of newline (ECHONL, normally off anyway) */ newterm.c_lflag &= ~(ICANON | ECHO | ECHONL); if (flags & TERMIOS_CLEAR_ISIG) { /* dont recognize INT/QUIT/SUSP chars */ newterm.c_lflag &= ~ISIG; } /* reads will block only if < 1 char is available */ newterm.c_cc[VMIN] = 1; /* no timeout (reads block forever) */ newterm.c_cc[VTIME] = 0; if (flags & TERMIOS_RAW_CRNL) { /* dont convert CR to NL on input */ newterm.c_iflag &= ~(IXON | ICRNL); /* dont convert NL to CR on output */ newterm.c_oflag &= ~(ONLCR); } if (flags & TERMIOS_RAW_INPUT) { /* dont convert anything on input */ newterm.c_iflag &= ~(BRKINT|INLCR|ICRNL|IXON|IXOFF|IUCLC|IXANY|IMAXBEL); } return tcsetattr(fd, TCSANOW, &newterm); } int FAST_FUNC tcsetattr_stdin_TCSANOW(const struct termios *tp) { return tcsetattr(NANO_STDIN_FD, TCSANOW, tp); } static int wh_helper(int value, int def_val, const char *env_name, int *err) { /* Envvars override even if "value" from ioctl is valid (>0). * Rationale: it's impossible to guess what user wants. * For example: "man CMD | ...": should "man" format output * to stdout's width? stdin's width? /dev/tty's width? 80 chars? * We _cant_ know it. If "..." saves text for e.g. email, * then it's probably 80 chars. * If "..." is, say, "grep -v DISCARD | $PAGER", then user * would prefer his tty's width to be used! * * Since we don't know, at least allow user to do this: * "COLUMNS=80 man CMD | ..." */ char *s = getenv(env_name); if (s) { value = atoi(s); /* If LINES/COLUMNS are set, pretend that there is * no error getting w/h, this prevents some ugly * cursor tricks by our callers */ *err = 0; } if (value <= 1 || value >= 30000) value = def_val; return value; }